[筆記] Swift深入淺出常見語法 – Error & NSError

man people art sign
分享

內容目錄

Error: Basic


// (1) Create a enum for 'Error'
enum AuthorizationError: Error {
  case accountNotExisted
  case accountBlocked(reason: String)
  case needTwoFactor
}

// (2) somewhere to 'throws' the error
func accountHandler(existed: Bool, status: String, need2Factor: Bool) throws -> Bool {
  guard existed else {
    throw AuthorizationError.accountNotExisted
  }
  guard status == "active" else {
    throw AuthorizationError.accountBlocked(reason: "be_reported_by_someone")
  }
  guard !need2Factor else {
    throw AuthorizationError.needTwoFactor
  }
  return true
}

// (3) 'do' the function and 'try' to 'catch' the error
func accountVerifyTest() {
    do {
      let _ = try accountHandler(existed: true, status: "blocked", need2Factor: false)
    } catch {
        print("Something wrong: \(error)")
    }
}

Error: Async / Closure

func accountVerifyApi(completion: @escaping (() throws -> Bool) -> Void) {
  // simulate a networking service api
  DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
    do {
        let result = try accountHandler(existed: true, status: "blocked", need2Factor: false)
        completion { return result }
    } catch {
        completion { throw error }
    }
  }
}

accountVerifyApi { (result) in
    do {
        let res = try result()
        print(res)
    } catch {
        print(error)
    }
}

Error => NSError

Error 相對 NSError 較為開放,但如果要將Error Message也封裝在Error的宣告範圍裡,就沒有像NSError那麼靈活,但!Swift 3之後,加了Error與NSError的完美Bridging,參考以下範例囉~

// 想為Error加上額外資訊
// 方法ㄧ: 使用extension 擴充方法
extension AuthorizationError {
    var getDetail: String {
        switch self {
        case .accountNotExisted: return "account not existed"
        case .accountBlocked(reason: let reason): return reason
        case .needTwoFactor: return "need two factor"
        }
    }
}
// 看起來很單純,但無法確保 mapping 可以如預期 
let aerror = error as! AuthorizationError
print(aerror.getDetail)

// 方法二: 使用NSError Bridging
// (1) LocalizedError
extension AuthorizationError: LocalizedError {
    var errorDescription: String? {
        switch self {
        case .accountNotExisted: return "account not existed"
        case .accountBlocked(reason: let reason): return reason
        case .needTwoFactor: return "need two factor"
        }
    }
}

// (2) CustomNSError
extension AuthorizationError: CustomNSError {
    public static var errorDomain: String {
        return "AuthorizationError"
    }

    public var errorCode: Int {
        switch self {
        case .accountNotExisted: return 1
        case .accountBlocked(reason: _): return 2
        case .needTwoFactor: return 3
        }
    }

    public var errorUserInfo: [String : Any] {
        switch self {
        case .accountNotExisted:
            return [ "message": "account not existed"]
        case .accountBlocked(reason: let reason):
            return [ "message": reason]
        case .needTwoFactor: return [ "message": "need two factor"]
        }
    }
}

// 第二種較複雜,但可以準確橋接到NSError
let nserror = error as NSError
print(nserror.localizedDescription) // LocalizedError
print(nserror.userInfo) // CustomNSError

throws vs rethrows

// (1) 使用 throws -> [T] 時
public func doubleElementArray<T>(_ transform: () throws -> T) throws -> [T] {
    let result = try transform()
    return [result, result]
}
// "必須"在呼叫函式前面加上 'try',否則compile不會過
let result = try doubleElementArray { () -> String in
    return "Peace"
}

// 但並非每個狀況都會遇到Exception,特別是使用泛型時
// (2) 因此可以改用 rethrows -> [T]
public func doubleArray<T>(_ transform: () throws -> T) rethrows -> [T] {
    let result = try transform()
    return [result, result]
}

// 可行
let doubleString = doubleArray { () -> String in
    return "Peace"
}
// 可行
let doubleInt = try doubleArray { () -> Int in
    throw DoubleArrayError.forceIntThrow
}

rethrows = return or throws,這樣看rethrows也許比較好理解,再配合Swift .map 的源碼:

func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

再問問自己,過去用.map時,有多少次用到try?很少吧!沒錯這就是rethrows默默付出的結果!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *