기존의 에러 핸들링(completionHandler)
기존의 escaping closure의 completionhandler를 통해서 비동기처리를 해줄때, error처리가 상당히 어려워진다.
예시로써
func fetchData(completion: @escaping (Data?, Error?) -> Void) {
//...
}
fetchData { (data, error) in
guard error == nil else { handleError(error!) }
guard let data = data else { return }
completion(data)
}
이러한 코드에서 실제로 예외처리가 가능한 경우의 수는 다음과 같다.
- Data, Error : True, False
- Data, Error : True, True
- Data, Error : False, True
- Data, Error : False, False
즉 실제로 우리가 처리하려고 하는 1번과 3번보다 더 애매한 혹은 이상한 에러가 발생할수 있다는 문제가 존재
이를 swift에서는 result type을 통해서 깔끔하게 해결할수 있다.
Result type
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
result란 이넘타입이며, where절을 이용해서 failure에서는 error가 들어와야한다고 정의되어 있다.
따라서 위의 코드를 다음과 같이 정리가 가능하다.
func fetchData(completion: @escaping (Result<Data>) -> Void) {
//...
}
fetchData { result in
switch result {
case .success(let data):
// load된 data 처리
case .failure(let error):
// error 처리
}
}
예외처리(Throws, Throw)를 통한 에러처리
- 예외처리가 필요한 이유는 optional타입은 오류가 발생했을 때 오류에 대한 정보를 외부로 전달할 방법이 없기 때문이다.
에러타입
Error타입은 모든 에러타입을 포함하기 때문에 정확하게 어떠한 에러가 발생했는지에 대해서 파악하기 어렵다.
이를 associated error를 정의하여 사용하면 좀더 좋은 에러핸들링이 가능하다.
예시는 다음과 같다.
에러타입 정의
enum testError: Error {
case overSizeError
case incorrectData(data: Int)
}
오류를 처리해줌
- throws는 해당 함수가 오류를 반환할수 있다는 의미
- throw는 return과 동일하게 에러를 반환한다는 의미이다.
// 오류가 나는 조건을 throws와 함께 배치
func throwError(someValue: Int) throws -> Int {
guard someValue <= 10 else {
throw DataError.overError
}
guard someValue >= 0 else {
throw DataError.incorrectData(data: someValue)
}
return someValue
}
do - try - catch
func getNextYear(inputNumber: Int) -> Int {
var number: Int = 0
do {
year = try throwError(someValue: number)
} catch DataError.overError {
print("값이 초과함")
} catch DataError.incorrectData(let part){
print("값이 정확하지 않음")
} catch {
print("default error catch")
}
return number
}
do try catch를 통해서 에러를 핸들링할수잇음
- do를 통해서 먼저 try를 함.
- try의 결과가 정상적이면 return number
- try에서 에러가 발생했는데 DataError.overError면 해당 클로저
- incorrectData면 해당 클로저로 감
try?, try!
이전에도 작성했지만,
try?
- 에러 발생 시 nil 반환합니다.
- 에러가 발생하지 않으면 반환 타입은 옵셔널(Optional)입니다.
- 반환 타입이 없어도 사용 가능합니다.
- do-catch문 없이 사용가능합니다.
try!
- 에러 발생시 crash
- 반환타입은 언래핑됨
Result와 associated error를 사용한 코드
1. 에러타입 정의
enum loadingError {
case .networkUnavailable
case .timedOut
case .invalidStatusCode(let code)
}
2. 에러를 result에 삽입
typealias Handler = (Result<Data, LoadingError>) -> Void
- 이렇게 typealias를 이용하면 코드가 좀더 보기 편하다.
3. 에러 핸들링
func fetchData(completion: @escaping Handler) { result in
switch result {
.success(let data):
self.completion(data)
.failure(let error):
switch error {
case .networkUnavailable:
self?.showErrorView(withMessage: .unavailable)
case .timedOut:
self?.showErrorView(withMessage: .timedOut)
case .invalidStatusCode(let code):
self?.showErrorView(withMessage: .statusCode(code))
}
}
참고
Uploaded by N2T