iOS Swift Combine Error Reactive Programming


這是一個基本的使用 Combine 的網路請求

func getData() -> AnyPublisher<Model, Error> {
    URLSession.shared.dataTaskPublisher(for: URL(string: "https://xxx.xx/xx")!)
        .map { $0.data }
        .decode(type: Model.self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

看起來很正常,成功給 model,失敗給 model

問題是這個 func return 的 error 會有好幾種

以這個範例來說 會有 URLError DecodingError

實際 sink 的使用端,並不會知道這裡會出現多少種 error

合理的設計是要明確定義有多少種 error

例如 建立一個 ApiError

enum ApiError: Error {
    case url(error: URLError)
    case decode(error: Error)
}

再把 func 修改一下

func getData() -> AnyPublisher<Model, Error> {
    URLSession.shared.dataTaskPublisher(for: URL(string: "https://xxx.xx/xx")!)
        .mapError({ error in
            ApiError.url(error: error)
        })
        .map { $0.data }
        .decode(type: Model.self, decoder: JSONDecoder())
        .mapError({ error in
            if type(of: error) == ApiError.self {
                return error
            } else {
                return ApiError.decode(error: error)
            }
        })
        .eraseToAnyPublisher()
}

修改成這樣的確是達成了效果 但是會寫大量相同的 mapError

讓我們把大量相同的 mapError 換成自己定義的 operator


再修改一次 getData function

func getData() -> AnyPublisher<Model, ApiError> {
    URLSession.shared.dataTaskPublisher(for: URL(string: "https://xxx.xx/xx")!)
        .asError(type: ApiError.self, { .url(error: $0) })
        .map { $0.data }
        .decode(type: Model.self, decoder: JSONDecoder())
        .asError(type: ApiError.self, { .decode(error: $0)})
        .eraseToAnyPublisher()
}

這樣實作上,簡單許多,也更容易閱讀


打完收工