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
등 코드 재사용성 및 가독성을 높이기 위한 방법