728x90
Error handling with mapError, setFailureType, & flatMap
Combine의 Publisher에서 Failure가 Never이지만, operator에서 Error을 처리해줘야하는 경우가 있을 수가 있다. 아래처럼 2가지 방법이 존재한다.
- Failure를 변환함.
- Error을 잘 처리해서, Failure가 없는 Never 타입으로 변경함.
샘플 코드를 통해 어떻게 catch, retry, setFailureType, replaceError(with), mapError를 살펴보자
샘플 코드는 url를 통해 이미지를 가져오는 예제이다.
시나리오는 아래와 같다.
- URL 주소 값은 string 타입의 FailureType이 Never인 Publisher이다.
- URL이 변경 될 때 이미지를 호출한다.
- 호출 된 이미지는 FailureType이 Never인 Publisher에 값을 보내준다.
- 이슈: url 주소값과 호출되는 이미지의 Publisher의 FailureType은 Never인데, URLSession 통신은 에러가 발생 할 수 밖에 없다.(Network연결이 안되어있거나, 이미지 서버가 내려갔거나, url 주소가 변경되서 동작이 안되거나 등등)
- 물론, 아무런 에러 처리를 안해주면 ios 입장에선 Failure가 Never이기 때문에 정상적으로 동작하지만 Error가 발생한다면 앱은 종료 되지 않을까?
그럼 에러 처리 방법을 알아보자.
- Catch
- Error가 발생 했을 시, 어떤 Publisher를 전달해줄지 설정할수 잇음. Empty()를 전달하면 빈 Publisher을 전달해주게됨
- retry
- 실패, Error가 발생 했을 경우 몇번 더 요청을 할 것인지 설정 할 수 있음.
- setFailureType
- publisher의 Error가 Never이더라도, setFailureType을 통해 publisher의 Error Type을 변경해줄 수 있음.
- publisher의 operator 중 URLSession.shared.dataTaskPublisher 있다면, Error가 발생 하는 Publisher임. 이럴 때 얘를 사용해서 URLError가 발생하는 Publisher로 변경이 가능함.
- replaceError(with)
- Error가 발생 했을 경우 어떤 것을 Publisher값으로 반환해줄지 지정가능.
- 지정을 해주었다면, Publisher Error는 Never로 설정됨.
- mapError
- 발생된 에러를 캐치하여, URLError인지, APIError인지 어떤 Error인지 식별 할 때 사용 할 수 있음.
- Error의 유형을 원하는대로 구분지어 변경 할 수 있음.
이제 사용법을 살펴보자.
Catch
먼저 catch가 제일 쉽다.
기본 문법
struct SimpleError: Error {}
let numbers = [5, 4, 3, 2, 1, 0, 9, 8, 7, 6]
cancellable = numbers.publisher
.tryLast(where: {
guard $0 != 0 else {throw SimpleError()}
return true
})
.catch({ (error) in
Just(-1)
})
.sink { print("\\($0)") }
****// Prints: -1
예제 문법
아래처럼 Empty()를 통해 빈 Publisher을 전달 해 줄 수 있다.
.map{ (url) in
URLSession.shared.dataTaskPublisher(for: url)
.map(\\.data)
.compactMap{
UIImage(data: $0)
}
.catch { _ in
// 에러 발생시 빈 Publisher로
Empty()
}
}
setFailureType
- error type을 변경 할 수 있음.
기본문법
let pub1 = [0, 1, 2, 3, 4, 5].publisher
let pub2 = CurrentValueSubject<Int, Error>(0)
let cancellable = pub1
.setFailureType(to: Error.self)
.combineLatest(pub2)
.sink(
receiveCompletion: { print ("completed: \\($0)") },
receiveValue: { print ("value: \\($0)")}
)
// Prints: "value: (5, 0)".
예제
// Publihser의 ErrorType을 변경해줌.
.setFailureType(to: URLError.self)
.map{ (url) in
URLSession.shared.dataTaskPublisher(for: url)
.map(\\.data)
.compactMap{
UIImage(data: $0)
}
.catch { _ in
Empty()
}
}
replaceError(with)
- error가 발생 했을 때, 대치 할 값을 설정할 수 있음.
기본문법
struct MyError: Error {}
let fail = Fail<String, MyError>(error: MyError())
cancellable = fail
.replaceError(with: "(replacement element)")
.sink(
receiveCompletion: { print ("\\($0)") },
receiveValue: { print ("\\($0)", terminator: " ") }
)
// Prints: "(replacement element) finished".
예제
.map{ (url) in
URLSession.shared.dataTaskPublisher(for: url)
.map(\\.data)
.compactMap{
UIImage(data: $0)
}
// error가 발생 했을 경우, 내부의 asset에 있는 사진으로 변경함.
.replaceError(with: UIImage(named: "pink")!)
}
mapError
- error 타입을 변경해서 전달함.
기본문법
struct DivisionByZeroError: Error {}
struct MyGenericError: Error { var wrappedError: Error }
func myDivide(_ dividend: Double, _ divisor: Double) throws -> Double {
guard divisor != 0 else { throw DivisionByZeroError() }
return dividend / divisor
}
let divisors: [Double] = [5, 4, 3, 2, 1, 0]
divisors.publisher
.tryMap { try myDivide(1, $0) }
.mapError { MyGenericError(wrappedError: $0) }
.sink(
receiveCompletion: { print ("completion: \\($0)") ,
receiveValue: { print ("value: \\($0)", terminator: " ") }
)
// Prints: "0.2 0.25 0.3333333333333333 0.5 1.0 completion: failure(MyGenericError(wrappedError: DivisionByZeroError()))"
예제
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
// error 타입을 변경해서 전달함.
APIResources.APIError.convert(error: error)
})
.eraseToAnyPublisher()
}
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 ""
}
}
반응형
'iOS' 카테고리의 다른 글
Combine - Operator(switchToLastest, FlatMap) (0) | 2022.04.19 |
---|---|
Combine - Operator(map, compactMap, tryMap) (0) | 2022.04.19 |
combine - 2. Network 예제 살펴보기 (0) | 2022.04.18 |
combine - 1. Combine에 관하여 (0) | 2022.03.14 |
iOS 동시성 (GCD Grand Central Dispatch) 기본 개념 (0) | 2022.02.07 |