[SwiftUI] CryptoApp: Network Service

Junyoung Park·2022년 11월 3일
0

SwiftUI

목록 보기
76/136
post-thumbnail

Add a reusable Networking layer | SwiftUI Crypto App #7

CryptoApp: Network Service

구현 목표

  • 네트워크 서비스를 담당하는 별도의 서비스 클래스

구현 태스크

  • URL을 통해 다운로드, 데이터를 리턴하는 함수
  • URLSession의 Response 담당 함수
  • 섭스크라이버의 컴플리션을 담당하는 함수
  • 커스텀 함수

핵심 코드

static func download(with url: URL) -> AnyPublisher<Data, Error> {
        return URLSession
            .shared
            .dataTaskPublisher(for: url)
            .subscribe(on: DispatchQueue.global(qos: .default))
            .tryMap({try handleURLResponse(output: $0, url: url)})
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
  • URL을 통해 URLSessions의 데이터 퍼블리셔 함수를 사용
  • eraseToAnyPublisher를 통해 간단한 퍼블리셔 타입으로 축약 가능
  • 재사용 가능성이 높은 코드를 서비스 클래스의 static func로 구현
static func handleURLResponse(output: URLSession.DataTaskPublisher.Output, url: URL) throws -> Data {
        guard
            let response = output.response as? HTTPURLResponse,
            response.statusCode >= 200 && response.statusCode < 300 else
        { throw NetworkingError.badURLResponse(url: url) }
        return output.data
    }
  • URLSessions의 데이터 퍼블리셔가 리턴하는 아웃풋 타입을 핸들링하는 함수
  • 커스텀 에러를 throw하거나 아웃풋의 해당 데이터를 리턴
  • 본래 코드에서 tryMap 코드 내부에서 하던 일을 재활용하기 위해 네트워크 매니저의 static func로 구현
static func handleCompletion(completion: Subscribers.Completion<Error>) {
        switch completion {
        case .failure(let error):
            print(error.localizedDescription)
        case .finished: break
        }
    }
  • sink 단의 컴플리션 문제가 일어났을 때 재활용하기 위한 코드

소스 코드

import Foundation
import Combine

class NetworkingManager {
    enum NetworkingError: LocalizedError {
        case badURLResponse(url: URL)
        case unknown
        var errorDescription: String? {
            switch self {
            case .badURLResponse(url: let url): return "[🔥] Bad Response from URL: \(url.absoluteString)"
            case .unknown: return "[⚠️] Unknown error occured"
            }
        }
    }
    
    static func handleURLResponse(output: URLSession.DataTaskPublisher.Output, url: URL) throws -> Data {
        guard
            let response = output.response as? HTTPURLResponse,
            response.statusCode >= 200 && response.statusCode < 300 else
        { throw NetworkingError.badURLResponse(url: url) }
        return output.data
    }
    
    static func download(with url: URL) -> AnyPublisher<Data, Error> {
        return URLSession
            .shared
            .dataTaskPublisher(for: url)
            .subscribe(on: DispatchQueue.global(qos: .default))
            .tryMap({try handleURLResponse(output: $0, url: url)})
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
    static func handleCompletion(completion: Subscribers.Completion<Error>) {
        switch completion {
        case .failure(let error):
            print(error.localizedDescription)
        case .finished: break
        }
    }
}
  • URLSession 데이터 퍼블리셔, URLResponse 핸들러, 컴플리션 핸들러
  • 커스텀 에러 구현
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 = NetworkingManager
            .download(with: url)
            .decode(type: [CoinModel].self, decoder: JSONDecoder())
            .sink(receiveCompletion: NetworkingManager.handleCompletion,
                  receiveValue: { [weak self] coinModels in
                self?.allCoins = coinModels
                self?.coinSubscription?.cancel()
            })
    }
  • download, handleCompletion 등 코드 재사용성 및 가독성을 높이기 위한 방법
profile
JUST DO IT

0개의 댓글