[Swift/LocalizedError] Error 메세지도 같이 커스텀하자.

[Swift/LocalizedError] Error 메세지도 같이 커스텀하자.

LocalizedError를 사용해서 Error 색다르게 먹기

웬만하면 코딩을 하면서 nil보다는 throw를 하려고 한다. 예를 들어

let dict = [String: String]()

func getValue(key: String) -> String? {
    guard key.count >= 10 else { return nil }
    return dict[key]
}

key는 반드시 10글자가 넘어야 된다고 가정하자.

위 코드를 getValue 함수가 nil반환 되는 경우는 크게 2가지다.

  1. key가 10글자가 안되는 경우

  2. dict에 key가 없는 경우

만약 저 함수를 사용하는 입장에서는

그래서 왜 nil이 반환되는 거지?

라는 의문을 품게된다. 그래서 그러한 오해의 소지가 없게

let dict = [String: String]()

func getValue(key: String) throws -> String {
    guard key.count >= 10 else { throw CustomError.keyIsShort }
    guard let result = dict[key] else { throw CustomError.notExist }
    return result
}

enum CustomError: Error {
    case keyIsShort
    case notExist
}

위 처럼 쓰면 사용자 입장에서는 왜 저 함수가 정상적으로 작동하지 않는지 알 수 있다.


여기서 다가오는 2번째 문제다. 저렇게 코딩하는 습관을 들여놓으니 do-catch가 귀찮지만

확실히 디버깅할 때 어디서 문제가 생겼는지 파악이 쉬워졌다.

하지만 2번째 문제가 여기서 생겼다.

내가 던지는 에러들에 대해서는 처리가 쉬웠지만, Framework에서 던지는 Error들에 대해서는 구분이 쉽지가 않다는 것이다.

Framework에서 던지는 Error에 대해서는 error.localizedDescription로 파악이 쉬웠다.

하지만 내가 던지는 Error에는 localizedDescription 이 없었기에

왜 에러가 일어났는지 localizedDescription으로 찍으면 print 바로 확인할 수 있다면

에러 처리가 더 수월해질 것 같아서 만들어보고자 함


LocalizedError

/// Describes an error that provides localized messages describing why
/// an error occurred and provides more information about the error.
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
public protocol LocalizedError : Error {

    /// A localized message describing what error occurred.
    var errorDescription: String? { get }

    /// A localized message describing the reason for the failure.
    var failureReason: String? { get }

    /// A localized message describing how one might recover from the failure.
    var recoverySuggestion: String? { get }

    /// A localized message providing "help" text if the user requests help.
    var helpAnchor: String? { get }
}

주석을 보면. 에러 왜 발생했는지 에러메세지도 같이 던지고 싶으면 쓰라고 적혀있음

설명이 잘 적혀 있으므로 주석을 직접 참조하면 좋을 것 같다.


Default는 print(error.localizedDescription)을 하면 다음과 같이 나온다.

The operation couldn’t be completed. (SwiftTest.CustomError error 0.)

The operation couldn’t be completed. : default - errorDescription

(SwiftTest.CustomError error 0.) failureReason

+)SwiftTest 프로젝트 - CustomError enum - 0번째 case 라는 의미

그럼 커스텀 해보자.

enum CustomError: LocalizedError {
    case keyIsShort
    case notExist

    var errorDescription: String? {
        switch self {
        case .keyIsShort: "Key is too short"
        case .notExist: "Value is not exist"
        }
    }

    var failureReason: String? {
        switch self {
        case .keyIsShort: "key.count < 10"
        case .notExist: "dict[key] == nil"
        }
    }

    var recoverySuggestion: String? {
        switch self {
        case .keyIsShort: "Key를 10글자 이상으로 하세요."
        case .notExist: "Value를 추가해주세요."
        }
    }

    var helpAnchor: String? {
        switch self {
        case .keyIsShort: "글자 더 많이 추가하세요"
        case .notExist: "데이터 추가 함수를 사용해보세요."
        }
    }
}

errorDescription 을 override하면 failureReason은 별도로 출력해야한다.

저는 그냥 errorDescription에 추가함

do {
    let a = try getValue(key: "dgdgsdgsdgds")

} catch let error as CustomError {
    print(error.localizedDescription)
    print(error.failureReason)
}
// Value is not exist
// dict[key] == nil
do {
    let a = try getValue(key: "dgdgsdgsdgds")

} catch {
    print(error.localizedDescription)
} 
// Value is not exist

Casting을 하면 localizedDescription 외 변수에 직접 접근이 가능하지만

그냥 쓰면 Error 타입이기에 직접 접근이 안된다.


참조한 사이트

김종권의 iOS 앱 개발 알아가기

Apple Developer Documentation