[UIKit] Combine: Error Handling

Junyoung Park·2022년 10월 26일
0

UIKit

목록 보기
54/142
post-thumbnail
post-custom-banner

Combine framework tutorial - Part 3 - Error handling with mapError, setFailureType, & flatMap

Combine: Error Handling

구현 목표

  • 퍼블리셔를 구독할 때의 에러 핸들링 기법 익히기

구현 태스크

  • tryMap 사용: 데이터 매핑 시 에러 발생 시 throw
  • mapError 사용: 특정 에러를 다른 데이터 타입으로 매핑

핵심 코드

func fetch(url: URL) -> AnyPublisher<Data, APIError> {
        return URLSession
            .shared
            .dataTaskPublisher(for: url)
            .tryMap { (data, response) -> Data in
                if let response = response as? HTTPURLResponse,
                   !(200...299).contains(response.statusCode) {
                    throw APIResources.APIError.badResponse(statusCode: response.statusCode)
                } else {
                    return data
                }
            }
            .mapError { error in
                APIResources.APIError.convert(error: error)
            }
            .eraseToAnyPublisher()
    }
  • URL 데이터를 입력받고 데이터 또는 커스텀 에러 타입의 데이터 퍼블리셔를 리턴하는 함수
  • tryMap을 통해 에러를 throw 또는 실제 데이터를 스트리밍
  • mapError를 통해 특정한 에러를 커스텀 에러 타입으로 캐스팅 가능
enum APIError: Error, CustomStringConvertible {
        case url(URLError?)
        case badResponse(statusCode: Int)
        case unknown(Error)
        
        static func convert(error: Error) -> APIError {
            switch error {
            case is URLError: return .url(error as? URLError)
            case is APIError: return error as! APIError
            default: return .unknown(error)
            }
        }
        
        var description: String {
            return ""
        }
    }
  • 이넘으로 관리
private func addSubscriber() {
        guard let url = URL(string: urlString) else { return }
        URLSession
            .shared
            .dataTaskPublisher(for: url)
            .receive(on: DispatchQueue.global(qos: .background))
            .tryMap { output in
                return output.data
            }
            .decode(type: [AlbumModel].self, decoder: JSONDecoder())
            .sink { completion in
                switch completion {
                case .finished: print("Successfully get album info")
                case .failure(let error): print(error.localizedDescription)
                }
            } receiveValue: { [weak self] returnedValue in
                guard let self = self else { return }
                self.albumSubject.send(returnedValue)
            }
            .store(in: &cancellables)
        imageUrlSubject
            .removeDuplicates()
            .compactMap { url in
                return URL(string: url)
            }
            .setFailureType(to: APIResources.APIError.self)
            .flatMap(maxPublishers: .max(4)) { url -> AnyPublisher<UIImage, APIResources.APIError> in
                self.apiResources
                    .fetch(url: url)
                    .compactMap({UIImage(data: $0)})
                    .catch { _ in
                        Empty()
                    }
                    .eraseToAnyPublisher()
            }
            .scan([UIImage]()) { all, next in
                return all + [next]
            }
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { completion in
            }, receiveValue: { [weak self] images in
                self?.imagesSubject.send(images)
            })
            .store(in: &cancellables)
    }
  • 이미지를 다운로드, 핸들링하는 뷰 모델에서 imageUrlSubject를 구독할 때 실패 형식을 APIError로 정의, 이후 flatMap 메소드에서 커스텀 타입의 퍼블리셔를 리턴
profile
JUST DO IT
post-custom-banner

0개의 댓글