[SwiftUI] CryptoApp: Data Service Class

Junyoung Park·2022년 11월 3일
0

SwiftUI

목록 보기
75/136
post-thumbnail
post-custom-banner

Download coins using a View Model and Data Service class | SwiftUI Crypto App #6

CryptoApp: Data Service Class

구현 목표

  • 뷰 모델이 다룰 데이터를 네트워크를 통해 패치
  • 데이터 서비스를 제공하는 별도의 매니저 클래스 구현

구현 태스크

  • 데이터 서비스 클래스 구현
  • 데이터 패치를 위한 URLSession 데이터 퍼블리셔 사용
  • 컴바인을 통한 @Published 구독

핵심 코드

coinSubscription = URLSession
            .shared
            .dataTaskPublisher(for: url)
            .subscribe(on: DispatchQueue.global(qos: .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
            }
            .receive(on: DispatchQueue.main)
            .decode(type: [CoinModel].self, decoder: JSONDecoder())
            .sink { [weak self] completion in
                switch completion {
                case .failure(let error):
                    print(error.localizedDescription)
                    self?.coinSubscription?.cancel()
                case .finished: break
                }
            } receiveValue: { [weak self] coinModels in
                self?.allCoins = coinModels
                self?.coinSubscription?.cancel()
            }
  • 데이터 서비스 클래스에서 URL 데이터를 통해 데이터를 패치, 해당 모델로 디코딩 이후 @Published 데이터에 해당 패치 데이터를 넣어주는 부분
  • cointSubscription의 타입은 AnyCancellable 옵셔널 타입으로 해당 구독을 이후 취소할 수 있음
  • 데이터를 받을 때에는 글로벌 스레드로, 직접 데이터를 줄 때에는 메인 스레드로 받기
 private func addSubcriber() {
        dataService
            .$allCoins
            .sink { [weak self] coinModels in
                self?.allCoins = coinModels
            }
            .store(in: &cancellables)
    }
  • 뷰 모델이 해당 데이터 서비스 클래스의 퍼블리셔를 구독하는 데에는 지속적으로 값을 저장하도록 store로 구현

소스 코드

import Foundation
import Combine

class CoinDataService {
    @Published var allCoins: [CoinModel] = []
    var coinSubscription: AnyCancellable?
    
    init() {
        fetchCoins()
    }
    
    private func fetchCoins() {
        guard let url = URL(string: "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250&page=1&sparkline=true&price_change_percentage=24h") else { return }
        coinSubscription = URLSession
            .shared
            .dataTaskPublisher(for: url)
            .subscribe(on: DispatchQueue.global(qos: .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
            }
            .receive(on: DispatchQueue.main)
            .decode(type: [CoinModel].self, decoder: JSONDecoder())
            .sink { [weak self] completion in
                switch completion {
                case .failure(let error):
                    print(error.localizedDescription)
                    self?.coinSubscription?.cancel()
                case .finished: break
                }
            } receiveValue: { [weak self] coinModels in
                self?.allCoins = coinModels
                self?.coinSubscription?.cancel()
            }
    }
}
import Foundation
import Combine

class HomeViewModel: ObservableObject {
    @Published var allCoins: [CoinModel] = []
    @Published var portfolioCoins: [CoinModel] = []
    private let dataService = CoinDataService()
    private var cancellables = Set<AnyCancellable>()
    init() {
        addSubcriber()
    }
    
    private func addSubcriber() {
        dataService
            .$allCoins
            .sink { [weak self] coinModels in
                self?.allCoins = coinModels
            }
            .store(in: &cancellables)
    }
}

구현 화면

profile
JUST DO IT
post-custom-banner

0개의 댓글