banner

서론

해당 포스트는 CombineNetworking 처리에 대해 정리한 내용입니다.

Networking

아래는 URLSession 을 이용하여 서버로 부터 데이터를 받고 json 데이터를 디코딩하고 AnyPublisher 로 반환하는 코드입니다.

struct Post: Codable {
    let userId: Int
    let id: Int
    let title: String
    let body: String
}

func fetchPosts() -> AnyPublisher<[Post], Error> {
    return URLSession.shared.dataTaskPublisher(for: URL(string: "https://jsonplaceholder.typicode.com/posts")!)
        .map(\.data)
        .decode(type: [Post].self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

let cancellable = fetchPosts()
    .receive(on: DispatchQueue.main).sink { completion in
    print(completion)
} receiveValue: { posts in
    print(posts)
}

dataTaskPublisher()

URLSession.shared.dataTaskPublisher(for: URL)URLSessiondataTaskPublisher(for: URL) 을 이용하여 URL 로 부터 데이터를 받아오는 Publisher 를 생성합니다.

eraseToAnyPublisher()

dataTaskPublisher() 의 리턴값은 단순한 Publisher 타입이 아닌 좀 더 복잡한 타입입니다. 하지만 데이터를 수신하는 쪽에서는 복잡한 타입이 아닌 단순한 Publisher 타입을 원합니다. 이때 eraseToAnyPublisher() 를 사용하여 복잡한 타입을 단순한 Publisher 타입으로 변환합니다.

만약 eraseToAnyPublisher() 를 사용하지 않으면 아래와 같은 타입의 Publisher 가 리턴됩니다.

func fetchPosts() -> Publishers.Map<Publishers.Decode<URLSession.DataTaskPublisher, [Post], JSONDecoder>, Data> {
    // 실제로는 더 복잡할 수도 있음
}

따라서 eraseToAnyPublisher() 를 호출하여 사용하기 단순한 AnyPublisher 타입으로 변환합니다.

Error Handling

아래는 네트워크 요청 중 발생하는 에러를 처리하는 코드입니다.

struct Post: Codable {
    let userId: Int
    let id: Int
    let title: String
    let body: String
}

enum NetworkError: Error {
    case badServerResponse
}

func fetchPosts() -> AnyPublisher<[Post], Error> {
    return URLSession.shared.dataTaskPublisher(for: URL(string: "https://jsonplaceholder.typicode.com/posts")!)
        .tryMap { _, _ in
            print("retries")
            throw NetworkError.badServerResponse

//            guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
//                throw NetworkError.badServerResponse
//            }
//            return data
        }
        .retry(3)
        .decode(type: [Post].self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

let cancellable = fetchPosts().receive(on: DispatchQueue.main).sink { completion in
    print(completion)
} receiveValue: { posts in
    print(posts)
}

tryMap 연산자를 이용해서 예외를 발생시키고 retry 연산자를 이용해서 지정된 횟수만큼 재시도합니다. 그리고 completion 을 받아서 에러를 처리합니다.

두 개의 Publisher(네트워크 요청)를 합치기

두 개의 Publisher 를 합치는 방법은 CombineLatest 연산자를 사용하면 됩니다.

아래 코드는 두 개의 Publisher 를 합치는 예제입니다.

struct MovieResponse: Decodable {
    let Search: [Movie]
}

struct Movie: Decodable {
    let title: String

    private enum CodingKeys: String, CodingKey {
        case title = "Title"
    }
}

func fetchMovies(_ searchTerm: String) -> AnyPublisher<[Movie], Error> {
    guard let url = URL(string: "https://www.omdbapi.com/?s=\(searchTerm)&page=2&apiKey=564727fa") else {
        fatalError("Invalid URL")
    }

    return URLSession.shared.dataTaskPublisher(for: url)
        .map(\.data)
        .decode(type: MovieResponse.self, decoder: JSONDecoder())
        .map(\.Search)
        .eraseToAnyPublisher()
}

let cancellable = Publishers.CombineLatest(fetchMovies("batman"), fetchMovies("superman"))
    .sink(receiveCompletion: { completion in
        print(completion)
    }, receiveValue: { batmanMovies, supermanMovies in
        print("Batman Movies: \(batmanMovies.count)")
        print("Superman Movies: \(supermanMovies.count)")
    })

fetchMovies() 함수를 이용해서 두 개의 Publisher 를 생성하고 CombineLatest 연산자를 이용해서 두 개의 Publisher 를 합칩니다. 그리고 두 개의 Publisher 가 모두 값을 방출하면 receiveValue 클로저가 호출됩니다.

댓글남기기