๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸŽ iOS

[SwiftUI] Service Layer to ViewModel with Combine

by ํ‹ด๋”” 2022. 11. 21.
728x90
๋ฐ˜์‘ํ˜•

์ฐธ๊ณ  ๋ฐ ์ถœ์ฒ˜ ์‚ฌ์ดํŠธ

https://www.youtube.com/watch?v=TlJUMVKtUhc&list=PLwvDm4Vfkdphbc3bgy_LpLRQ9DDfFGcFu&index=7 

 

Service layer์˜ ์—ญํ™œ

- ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค๋ฅผ ๋‹ค๋ฃจ๋Š” ๋ชจ๋“  ๋กœ์ง์„ ํฌํ•จํ•œ ๋„คํŠธ์›Œํฌ ๋ ˆ์ด์–ด

- ๋ฐ์ดํ„ฐ๋ฅผ ๋””์ฝ”๋”ฉ ํ•˜๊ณ  ๋ทฐ ๋ชจ๋ธ๋กœ ์ „๋‹ฌํ•˜๋Š” ์—ญํ™œ์„ ํ•จ

 

Services ์ƒ์„ฑ

- New file > Swift ํŒŒ์ผ ์„ ํƒ ํ›„ PrefixDataService.swift ํŒŒ์ผ ์ƒ์„ฑ

 

Published ๋ณ€์ˆ˜ ์„ ์–ธ

@Published var allCoins: [CoinModel] = []

- @Published ํ”„๋กœํผํ‹ฐ ๋žฉํผ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํผ๋ธ”๋ฆฌ์…”๋ฅผ ์ƒ์„ฑํ•จ

- subscriber๊ฐ€ ํผ๋ธ”๋ฆฌ์…”๋กœ ์„ ์–ธ๋œ ๋ณ€์ˆ˜๋ฅผ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ์Œ

 

Cancellable์ถ”๊ฐ€

- Rx์—์„œ disposeBag์˜ ์—ญํ™œ. ๋ฉ”๋ชจ๋ฆฌ์—์„œ ํ•ด์ง€

var coinSubscription: AnyCancellable?

- cancellable์ด ๋ช…ํ™•ํžˆ ํ•˜๋‚˜์ผ ๊ฒฝ์šฐ ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์—ฌ๋Ÿฌ๊ฐœ์ผ ๊ฒฝ์šฐ ์•„๋ž˜์™€ ๊ฐ™์ด ์„ ์–ธํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ

var cancellables = Set<AnyCancellable>()

 

Initializer ์ •์˜

    init() {
        getCoints()
    }

- ์ด๋‹ˆ์…œ๋ผ์ด์ €์—์„œ ํ•ด๋‹น ๊ฐ์ฒด ์ƒ์„ฑ์‹œ ํ˜ธ์ถœ๋  ํ•จ์ˆ˜๋ฅผ ์ง€์ •ํ•ด ์คŒ

 

function to request data

guard let url = URL(string: "https://api.~~~~") else { return }

- api๋ฅผ ๋ฐ›์•„์˜ฌ url์ด ์œ ํšจํ•œ์ง€ ๊ฒ€์ฆ. url์— ๋ฐ”์ธ๋”ฉ

 

coinSubscription = URLSession.shared.dataTaskPublisher(for: url)

- cancellable์— publisher๋ฅผ ๋„˜๊ฒจ์คŒ. 

- URLSession shared์— dataTaskPublisher๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํผ๋ธ”๋ฆฌ์…” ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ

 

.subscribe(on: DispatchQueue.global(qos: .default))

- ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์šด๋กœ๋“œ ๋ฐ›๊ธฐ ์œ„ํ•ด ์Šค๋ ˆ๋“œ ์ •์˜. global default๋กœ ์„ค์ •ํ•ด์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ž‘์—…ํ•˜๋„๋ก ์„ค์ •

 

            .tryMap { (output) -> Data in
                
                guard let response = output.response as? HTTPURLResponse,
                      response.statusCode >= 200 && response.statusCode < 300 else {
                    throw URLError(.badServerResponse)
                }
                return output.data
            }

- tryMap์„ ์‚ฌ์šฉํ•ด์„œ ์—…์ŠคํŠธ๋ฆผ ํผ๋ธ”๋ฆฌ์…”๋กœ ๋ถ€ํ„ฐ ์ „๋‹ฌ ๋ฐ›์€ ๋ฐ์ดํ„ฐ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•˜๊ณ  Data๋ฅผ ๋ฐ˜ํ™˜ํ•จ

 

.receive(on: DispatchQueue.main)

- ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ui์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก main ์Šค๋ ˆ๋“œ๋กœ ๋ณ€๊ฒฝ

 

.decode(type: [CoinModel].self, decoder: JSONDecoder())

- type์— Codable ๊ฐ์ฒด๋ฅผ ์ง€์ •ํ•ด์„œ JSONDecodingํ•ด์คŒ

 

            .sink { completion in
                switch completion {
                case .finished:
                    break
                case .failure(let error):
                    print(error.localizedDescription)
                }
            } receiveValue: { [weak self] returnedCoins in
                self?.allCoins = returnedCoins
                self?.coinSubscription?.cancel()
            }

- ๋ฐฉ์ถœ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ๋…. error๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด failure ์ผ€์ด์Šค๋ฅผ, ์„ฑ๊ณตํ•˜๋ฉด receiveValue์— ์ „๋‹ฌํ•œ ํด๋กœ์ ธ๋ฅผ ํƒ€๊ฒŒ ๋จ

- ํ•ด๋‹น ํด๋ž˜์Šค์™€ ๊ฐ•ํ•œ์ฐธ์กฐ๊ฐ€ ๊ฑธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— weak self๋กœ ์•ฝํ•œ ์ฐธ์กฐ๋ฅผ ๊ฑธ์–ด์คŒ

- ํ•ด๋‹น ํผ๋ธ”๋ฆฌ์…”๋Š” ๋ฐ์ดํ„ฐ๋ฅผ 1ํšŒ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์•„ viewModel๋กœ ์ „๋‹ฌํ•  ์šฉ๋„์ด๋ฏ€๋กœ ์ตœ์ดˆ 1ํšŒ ํ˜ธ์ถœ ํ›„ ์žฌ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ
๋”ฐ๋ผ์„œ ํผ๋ธ”๋ฆฌ์…” ๋ณ€์ˆ˜์— ์ „๋‹ฌํ•œ ๋’ค cancellable์ธ coinSubscription์„ cancel ํ•ด์คŒ

728x90
๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€