[combine] 4. combine + URLSession

miori·2022년 10월 5일
0

ios-combine

목록 보기
5/5
post-thumbnail

애플의 firstparty framework : combine 정복

이번글에서는 combine + URLSession을 사용하여 네트워킹을 하는 법을 정리해보려 한다. 특히 URLSession 과 combine + URLSession을 비교해보며 작성해보려한다.

우선, 시작에 앞서 간단히 URLSession에 대해 복기해보자.

URLSession

URLSession은 iOS에서 HTTP 통신을 도와주는 API이다.
URLSession을 통해 서버와 통신을 할때, 우선 URLSessionConfiguration을 결정하고, Session을 생성한다.

코드

enum NetworkError: Error {
  case invalidRequest
  case transportError(Error)
  case responseError(statusCode: Int)
  case noData
  case decodingError(Error)
}

struct IdusInfo:Codable {
    let resultCount: Int
    let results: [IdusDetailInfo]
}
struct IdusDetailInfo: Codable {
    let sellerName: String
    let description: String
    let screenshotUrls: [String]
}

final class NetworkService {
    let session: URLSession
    
    //네트워크 서비스가 생성될때 session 받도록 생성
    init(configuration: URLSessionConfiguration) {
        session = URLSession(configuration: configuration)
    }
    
    func fetchInfo(appID: Int, completion: @escaping (Result<IdusInfo, Error>) -> Void) {
        
        let url = URL(string: "http://itunes.apple.com/lookup?id=\(appID)")!

        let task = session.dataTask(with: url) { data, response, err in
            
            if let err = err {
                completion(.failure(NetworkError.transportError(err)))
                return
            }
            
            if let httpResponse = response as? HTTPURLResponse,
                  !(200..<300).contains(httpResponse.statusCode) {
                completion(.failure(NetworkError.responseError(statusCode: httpResponse.statusCode)))
                return
            }
        
            guard let data = data else {
                completion(.failure(NetworkError.noData))
                return
            }
            
            //data -> 우리가 만든 모델
            do {
                let decoder = JSONDecoder()
                //응 IdusInfo 형태로 디코딩 할거야 from : data 를
                //실패할수 있으므로 try
                let idus = try decoder.decode(IdusInfo.self, from: data)
                completion(.success(idus))
            } catch let error as NSError {
                completion(.failure(NetworkError.decodingError(error)))
            }
        }

        task.resume()
    }
}

// -- network 담당하는 network service
// NetworkService 이용한 network작업

let networkService = NetworkService(configuration: .default)
networkService.fetchInfo(appID: 872469884) { result in
    switch result {
    case .success(let idus):
        print("Idus: \(idus)")
    case .failure(let error):
        print("Error:\(error)")
    }
}

URLSessionConfiguration에는 .default,.ephemeral,.background 가 있다.

  • .default
    • 디스크를 이용한 정보 저장을 하는 configuration
    • 그냥 브라우저 띄울때 주로 사용한다
  • .ephemeral
    • 쿠키나 인증서 같은 정보들을 저장하지 않는다
    • 크롬의 시크릿모드랑 비슷하다
  • .background
    • 네트워크를 통해 파일 다운로드 받을때 주로 사용한다
    • 앱이 백그라운드에서 돌때도, 다운로드 시켜줄수 있다

Combine + URLSession

진행 로직

Combine은 URLSessioin을 통해 받아온 데이터를 Publish할 수 있게끔 Publihser인 URLSession.DataTaskPublisher을 제공한다.

전달받은 데이터를 우리가 미리 선언해둔 type으로 변환할 수 있게끔 Operator를 제공한다.
URLSession.DataTaskPublisher 메서드는 task 성공시 결과값으로 data, response를 전달한다. 전달받은 값을 tryMap(_:) 연산자를 통해 에러를 throw하고 데이터를 뽑아낼 수 있다.
그리고 tryMap(_:) 을 거쳐 받은 데이터를 decode를 통해 원하는 형태로 decode 할 수 있다.

코드

import Combine

enum NetworkError: Error {
    case invalidRequest
    case responseError(statusCode: Int)
}

struct APIModel {
    static let scheme = "http"
    static let host = "itunes.apple.com"
    static let path = "/lookup"
    
    func searchApp(query: Int) -> URLComponents {
        var components = URLComponents()
        components.scheme = APIModel.scheme
        components.host = APIModel.host
        components.path = APIModel.path
        components.queryItems = [
            URLQueryItem(name: "id", value: "\(query)")
        ]
        return components
    }
}
    
struct IdusInfo:Codable {
    let resultCount: Int
    let results: [IdusDetailInfo]
}
struct IdusDetailInfo: Codable {
    let sellerName: String
    let description: String
    let screenshotUrls: [String]
}

final class NetworkService {
    let session: URLSession
    let api = APIModel()
    //네트워크 서비스가 생성될때 session 받도록 생성
    init(configuration: URLSessionConfiguration) {
        session = URLSession(configuration: configuration)
    }
    
    func getURL(appID: Int) throws -> URL {
        guard let url = api.searchApp(query: appID).url else {
            throw NetworkError.invalidRequest
        }
        return url
    }
    
    func fetchInfo(appID: Int) -> AnyPublisher<IdusInfo, Error> {
        let url = URL(string: "http://itunes.apple.com/lookup?id=\(appID)")!

        //dataTask Publisher
        let publisher = session
            .dataTaskPublisher(for: url)
        // 서버에서 받은 response 확인
            .tryMap { result -> Data in
                guard let httpResponse = result.response as? HTTPURLResponse,
                      (200..<300).contains(httpResponse.statusCode) else {
                    
                    let response = result.response as? HTTPURLResponse
                    let statusCode = response?.statusCode ?? -1
                    throw NetworkError.responseError(statusCode: statusCode)
                }
                return result.data
            }
        // 받은 data  디코딩 잘하기
            .decode(type: IdusInfo.self, decoder: JSONDecoder())
            .eraseToAnyPublisher() //type 지우는 역할
        return publisher
    }
}

// -- network 담당하는 network service
// NetworkService 이용한 network작업

let networkService = NetworkService(configuration: .default)

// 퍼블리셔 구독
let subscription = networkService
    .fetchInfo(appID: 872469884) //퍼블리셔
    .receive(on: RunLoop.main)
    .sink { completion in
        print("completion: \(completion)")
    } receiveValue: { info in
        print("Info :\(info)")
    }

비교

우선 제일 크게 느낀점은 Operator를 사용할 수 있다는 점이었다.

세세히 비교해보자면, dataTask(with:completionHandler:) 의 경우는 completion handler 클로저를 사용하여 추후 코드 작업을 진행했어야 했다.
하지만 combine을 사용하게 되면, Operator를 사용해 작업을 진행해주기 때문에 훨씬 직관적이라고 생각한다.


Refernce

  • 왕초보를 위한 한 번에 끝내는 iOS 앱 개발 바이블 초격차 패키지 Online.
profile
iS를 공부하는 miori 입니다.

0개의 댓글