ํ”„๋กœ์ ํŠธ ๐Ÿ› ๏ธ/iOS

URLSession + Combine ์กฐํ•ฉ์œผ๋กœ ๋„คํŠธ์›Œํฌ ๋ ˆ์ด์–ด ๋งŒ๋“ค๊ธฐ

WonderPark 2023. 6. 10. 02:25

 

์šฐ๋ฆฌ๊ฐ€ Firebase Firestore RestAPI ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ,

๊ธฐ์กด์— ์‚ฌ์šฉํ–ˆ๋˜ SDK ๋ฅผ ์‚ฌ์šฉํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ๋‹ค์‹œ ์ƒˆ๋กœ ๊ตฌํ˜„ํ•ด์•ผํ–ˆ๋‹ค.

 

๋จผ์ € ์šฐ๋ฆฌ๋Š” Firestore๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ ์˜ค๊ณ , Firestore ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๊ธฐ ์œ„ํ•ด

URLSession ์„ ์‚ฌ์šฉํ•ด์„œ ๋„คํŠธ์›Œํฌ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

 

๋„คํŠธ์›Œํฌ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์–‘ํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฐฉ์‹์ค‘ Combine์„ ์ฑ„ํƒํ•˜์—ฌ

Combine ๊ณผ URLSession ์ด ๋‘˜ ์กฐํ•ฉ์„ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

๋จผ์ € ํฐ ๊ตฌ์กฐ๋กœ ํ•œ๋ฒˆ ์ƒ๊ฐํ•ด๋ณด๋ฉด, ๐Ÿค”

  1. URL ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ ์ž‘์—…์„ ์œ„ํ•œ Publisher ์™€
  2. Publisher ๋ฅผ ๊ตฌ๋…ํ•ด์„œ Publisher์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๊ฐ’์„ ์ฒ˜๋ฆฌํ•  Subscriber ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

 

๊ทธ๋ž˜์„œ ์–ด๋–ป๊ฒŒ ๋„คํŠธ์›Œํฌ ๋ ˆ์ด์–ด๋ฅผ ๊ตฌํ˜„ํ•  ๊ฒƒ์ด๋ƒ ! ๐Ÿค”

  1. ์ €๋Š” ๋จผ์ € ์ƒ์„ฑ๋œ URL ์š”์ฒญ์„ ์‚ฌ์šฉํ•˜์—ฌ Publisher๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๋ฉ”์„œ๋“œ์™€
  2. ์ƒ์„ฑ๋œ Publisher๋ฅผ ๊ตฌ๋…ํ•˜๊ณ , ๋ฐœ์ƒํ•˜๋Š” ๊ฐ’์„ ์ฒ˜๋ฆฌํ•  ๋ฉ”์„œ๋“œ๋ฅผ ๋‚˜๋ˆ ์„œ ๊ตฌํ˜„ํ•  ๊ฒ๋‹ˆ๋‹ค!

 

 

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ๋Š” MVVM ํŒจํ„ด์œผ๋กœ ๊ตฌ์„ฑ์„ ํ–ˆ๊ณ , ์ „์ฒด์ ์ธ ๋„คํŠธ์›Œํฌ ์ž‘์—… ๋ ˆ์ด์–ด๋Š” ์ด๋ ‡๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ‘†๐Ÿป

 

 

๋จผ์ € Publisher ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๋ฉ”์„œ๋“œ๋Š” ์ด๋ ‡์Šต๋‹ˆ๋‹ค.

static func getUser(nextPageToken: String) -> AnyPublisher<UserResponse, Error> {
        do {
            let request = try UserRouter.get(nextPageToken: nextPageToken).asURLRequest()
            return URLSession
                .shared
                .dataTaskPublisher(for: request)
                .map{ $0.data }
                .decode(type: UserResponse.self, decoder: JSONDecoder())
                .eraseToAnyPublisher()
        } catch {
            return Fail(error: HTTPError.requestError).eraseToAnyPublisher()
        }
    }

 

 

dataTaskPublisher ?

๋จผ์ € URLSession ์˜ ๋ฉ”์„œ๋“œ์ธ dataTaskPublisher(for:) ๋ฅผ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ์š”!

 

์ด dataTaskPublisher(for:) ๋Š” ๋ฌด์—‡์„ ํ•˜๋Š” ์นญ๊ตฌ๋ƒ๋ฉด ?

 

URLSession์˜ ๋ฉ”์„œ๋“œ์ด๊ณ , Combine ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” Combine Publisher๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์ด Publisher๋ฅผ ์‚ฌ์šฉํ•ด ์šฐ๋ฆฌ๋Š” ๋น„๋™๊ธฐ์ ์ธ ๋„คํŠธ์›Œํฌ ๋ฐ์ดํ„ฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ณ , ๊ฒฐ๊ณผ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”๊ฒƒ์ด์ฃ !

 

dataTaskPublisher(for:) ๊ฐ€ ๋ฐฉ์ถœํ•œ Publisher๋Š” ๋ฐ์ดํ„ฐ ์ž‘์—…์ด ์‹œ์ž‘๋˜๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ ,

์ž‘์—…์ด ์™„๋ฃŒ๋˜๋ฉด ์™„๋ฃŒ ์ด๋ฒคํŠธ๋ฅผ ๋ฐฉ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ž‘์—… ๋„์ค‘์— ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜๋Š” ์—๋Ÿฌ ์ด๋ฒคํŠธ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

 

 

 

(์œ„์˜ ๋ฉ”์„œ๋“œ,,, ๋„ˆ๋ฌด ,,, ๋ญ๊ฐ€ ๋งŽ์ฃ  ? ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณผ๊ฒŒ์š” !!! ๐Ÿค“)

  1. UserRouter.get(nextPageToken: nextPageToken).asURLRequest(): UserRouter๋Š” URL ์š”์ฒญ์„ ์ƒ์„ฑํ•˜๋Š” ์—ด๊ฑฐํ˜• ๋ผ์šฐํ„ฐ์—์„œ get(nextPageToken:) ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ URL ์š”์ฒญ์„ ์ƒ์„ฑ.
  2. URLSession.shared.dataTaskPublisher(for: request): ์ƒ์„ฑ๋œ URL ์š”์ฒญ์„ ์‚ฌ์šฉํ•ด์„œ dataTaskPublisher(for:) ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœ.
  3. .map{ $0.data}: Publisher๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ (data: Data, response: URLResponse) ํŠœํ”Œ์„ ๋ฐ›์•„ ๋ฐ์ดํ„ฐ ๋ถ€๋ถ„๋งŒ ์ถ”์ถœ.  .data ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผ.
  4. .decode(type: UserResponse.self, decoder: JSONDecoder()): ์ถ”์ถœํ•œ ๋ฐ์ดํ„ฐ๋ฅผ JSONDecoder() ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ JSON ๋ฐ์ดํ„ฐ๋ฅผ UserResponse ํƒ€์ž…์œผ๋กœ ๋””์ฝ”๋”ฉ.
  5. .eraseToAnyPublisher(): decode ๋ฉ”์„œ๋“œ์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์€ Publishers.Decode ํƒ€์ž…์ด์ง€๋งŒ, ๋ฐ˜ํ™˜ ํƒ€์ž…์„ AnyPublisher<UserResponse, Error>๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด eraseToAnyPublisher()๋ฅผ ํ˜ธ์ถœ.
  6. return Fail(error: HTTPError.requestError).eraseToAnyPublisher(): ์š”์ฒญ ์ƒ์„ฑ ๋˜๋Š” ๋„คํŠธ์›Œํฌ ์ž‘์—… ์ค‘์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ, HTTPError.requestError๋ฅผ ๊ฐ€์ง„ Fail Publisher๋ฅผ ๋ฐ˜ํ™˜. ์ด ๊ฒฝ์šฐ์—๋„ eraseToAnyPublisher()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ฐ˜ํ™˜ ํƒ€์ž…์„ AnyPublisher<UserResponse, Error>๋กœ ๋ณ€ํ™˜.

 

getUser ๋ฉ”์„œ๋“œ์˜ ๋ฐ˜ํ™˜๊ฐ’์€ ๊ฒฐ๊ตญ AnyPublisher<UserResponse, Error> ๊ฐ€ ๋˜๋Š”๋ฐ,

 

์ด๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์˜ ๊ฒฐ๊ณผ๊ฐ€ 

  • ์„ฑ๊ณต์ ์ธ ๊ฒฝ์šฐ UserResponse ํ˜•์‹์œผ๋กœ
  • ์‹คํŒจํ•œ ๊ฒฝ์šฐ Errorํ˜•์‹ ์œผ๋กœ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค.

 

์ด๋ฅผ ํ†ตํ•ด ์ž‘์—…์˜ ์„ฑ๊ณต ๋˜๋Š” ์‹คํŒจ์— ๋”ฐ๋ผ ์ ์ ˆํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋Š”๊ฑฐ์ฃ !

 

์ด๋ ‡์Šต๋‹ˆ๋‹ค … ์ •๋ง ,,, ์‰ฝ์ฃ ์ž‰ ?

 

 

๊ทธ๋Ÿผ Publisher๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ๊ตฌ๋…(Subscription) ์€ ์–ด๋–ป๊ฒŒ ํ• ๊นŒ์š” ?

๊ทธ๋ ‡๋‹ค๋ฉด ์œ„์˜ ๋ฉ”์„œ๋“œ์—์„œ Publisher๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์ด์ œ ์–ด๋–ค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด์•ผํ• ๊นŒ์š” ?

์ด ๋ฐœํ–‰๋œ Publisher๋ฅผ ๊ตฌ๋…ํ•˜์—ฌ ์ž‘์—…์ด ์ˆ˜ํ–‰๋˜๋ฉด, ๋ฐœ์ƒ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•ด์•ผ๊ฒ ์ฃ !

 

๋ฐ”๋กœ ์ด ์ž‘์—…์€ ViewModel ์•ˆ์—์„œ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. !

  1. ๋จผ์ € ViewModel ์•ˆ์— ์œ ์ € ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ , ๋ฐ์ดํ„ฐ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  “users” (@Published) ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•ด์ค๋‹ˆ๋‹ค.
  2. fetchUsersSuccess ๋ผ๋Š” PassthroughSubject ๋ฅผ ์„ ์–ธํ•ด์ค๋‹ˆ๋‹ค.

PassthroughSubject ๋„ publisher์ด๊ณ , ์ž‘์—…์ด ๋๋‚˜๋ฉด send ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์ƒˆ ๊ฐ’์„ ๋ฐœํ–‰์‹œ์ผœ ์ค„๊ฒ๋‹ˆ๋‹ค.

 

@Published var users = [UserDocument]()
var fetchUsersSuccess = PassthroughSubject<[UserDocument], Never>()
func fetchUser() {
        UserService.getUser()
            .receive(on: DispatchQueue.main)
            .sink { (completion: Subscribers.Completion<Error>) in
             // ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฝ”๋“œ ์ž‘์„ฑ
              switch completion {
              case .finished:
                  print("์ž‘์—…์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
              case .failure(let error):
                  print("์—๋Ÿฌ ๋ฐœ์ƒ: \(error)")
            } receiveValue: { (data: UserResponse) in
              // ๋ฐ›์€ ๊ฐ’ ์ฒ˜๋ฆฌ 
              users = data.document   
              self.fetchUsersSuccess.send(data.documents)
            }.store(in: &subscription)
    }

 

๋จผ์ € fetchUser ๋ฉ”์„œ๋“œ์—์„œ UserSerivce.getUser() ๋ฅผ ํ˜ธ์ถœํ•ด์„œ Publisher๋ฅผ return ํ•˜๋ฉด

sink ๋ฅผ ํ†ตํ•ด ๊ตฌ๋…์„ ํ•˜๊ฒŒ ๋˜๋Š”๋ฐ์š”,

 

sink ?

์—ฌ๊ธฐ์„œ ์ฃผ๋ชฉํ•ด์•ผํ•  ๋ฉ”์„œ๋“œ๋Š” sink ์ž…๋‹ˆ๋‹ค ! ํ•œ๋ฒˆ ์•Œ์•„๋ณผ๊ฒŒ์š” 

๋ฐ”๋กœ ์ด sink ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, puslisher ์™€ subscriber ๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” subscription์„ ์•„์ฃผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋˜ ์ด sink ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ Publisher ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๊ฐ’ ๋˜๋Š” ์™„๋ฃŒ ๋ฐ ์—๋Ÿฌ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

sink ๋ฉ”์„œ๋“œ์˜ receiveCompletion ํด๋กœ์ ธ์—์„œ completion ๋งค๊ฐœ๋ณ€์ˆ˜ํƒ€์ž…์€

Subscribers.Completion<Error> ์ž…๋‹ˆ๋‹ค . Subscribers.Completion์€ Combine ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์™„๋ฃŒ ๋ฐ ์—๋Ÿฌ ์ด๋ฒคํŠธ๋ฅผ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ์—ด๊ฑฐํ˜•์ž…๋‹ˆ๋‹ค.

 

์ด ์—ด๊ฑฐํ˜•์€ ์•„๋ž˜ ๋‘๊ฐ€์ง€ ์ผ€์ด์Šค๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ  ๊ฐ๊ฐ์˜ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

  1. .finished : ์ž‘์—…์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋œ ๊ฒฝ์šฐ
  2. .failure : ์ž‘์—…์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์ง€ ๋ชปํ•œ ์ฑ„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ

 

 

receivedValue ?

sink ๋ฉ”์„œ๋“œ ๋ฐ”๋กœ ๋‹ค์Œ์— ๋ณด์ด๋Š” receivedValue ํด๋กœ์ ธ๋Š”

sink ๋ฅผ ํ†ตํ•ด pulisher ๋ฅผ ๊ตฌ๋…ํ•˜๊ณ , Publisher๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐœ์ƒํ–ˆ์„๋•Œ ํ˜ธ์ถœ๋˜๊ณ ,

์ด ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋ฉด ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ VM ์— ํ”„๋กœํผํ‹ฐ์ธ users ๋ฐฐ์—ด์— ์ €์žฅํ•ด์ฃผ๊ฒŒ ๋˜๋Š”๊ฑฐ์ฃ !

 

 

 

 

์—ฌ๊ธฐ๊นŒ์ง€ URLSession ๊ณผ Combine์„ ์‚ฌ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€ ์ฒ˜๋ฆฌ ํ•˜๋Š” ๊ณผ์ •์„ ์•Œ์•„๋ดค์Šต๋‹ˆ๋‹ค.

 

๋‹ค์Œ์€ URLSession ์˜ URL ์„ ๋งŒ๋“œ๋Š” ๊ณผ์ •์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณผ๊ฒŒ์šฉ

(์œ„์— ๋„คํŠธ์›Œํฌ ๋ ˆ์ด์–ด์— ์žˆ๋Š” Router ๋Š” ๋ญํ•˜๋Š” ์นญ๊ตฌ์ผ๊นŒ ? )

To be Continue ~

 

 

 

์˜ค๋Š˜๋„ ํ•ด๊ฒฐ์™„ ~ ๐Ÿ˜ƒ