1. 根底介绍 Swift Error(上)
  2. 重构优化 Swift Error(下)

在开发中,往往最容易被疏忽的内容便是对过错的处理。有经历的开发者,能够对自己写的每行代码担任,并且十分清楚自己写的代码在什么时候会呈现反常,这样就能提早做好过错处理。

Error

界说

Swift 里面的Error是一个协议

public protocol Error : Sendable { }

Sendable: 能够安全地将可发送类型的值从一个并发域传递到另一个——例如,您能够在调用参与者的办法时将可发送值作为参数传递

概述

Any type that declares conformance to the Error protocol can be used to represent an error in Swift’s error handling system. Because the Error protocol has no requirements of its own, you can declare conformance on any custom type you create.

在Swift的过错处理系统中,任何声明契合Error协议的类型都能够用来表明过错。由于Error协议没有它自己的要求,所以您能够对您创立的任何自界说类型声明共同性。

用枚举来表明简略的过错

Swift 的枚举十分合适表明简略的过错。创立一个契合Error协议的枚举,并为每个或许的过错供给一个case。假如需求有关过错的其他详细信息,能够运用相关值来包括该信息。

/// 声明一个Int解析的Error
enum IntParsingError: Error {
 /// 超越长度
 case overflow
 /// 无法解析的字符
 case invalidInput(String)
 /// 其他过错类型
 case other
}
extension Int {
 init(validating input: String) throws {
   for item in input {
     guard let _ = item.toInt() else {
       throw IntParsingError.invalidInput(String(item))
     }
   }
   if let int = input.toInt() {
     self = int
   } else {
     throw IntParsingError.other
   }
 }
}
​
extension Character {
 func toInt() -> Int? {
   let str = String(self)
   if let int = Int(str) {
     return int
   }
   return nil
 }
}
​
​
extension String {
 public func toInt() -> Int? {
   if let num = NumberFormatter().number(from: self) {
     return num.intValue
   } else {
     return nil
   }
 }
}
let money: String = "100块钱"
let a = try? Int(validating: money)

此刻的 a 是一个Int?类型, 初始化失利就回来nil.

do {
 let price = try Int(validating: money)
 print(price)
} catch IntParsingError.invalidInput(let invalid) {
 print("Invalid character: '(invalid)'")
} catch IntParsingError.overflow {
 print("Overflow error")
} catch {
 print("Other error")
}

此刻的price是一个Int类型, 假如转化失利,就会抛出反常。

用结构体或其他类型表明杂乱的过错

以下示例在解析时,运用结构来表明过错,包括产生过错的文件,办法和行号:

struct ParseError: Error {
 enum errorKind {
   case pathError
   case InvalidFormat
   case other
 }
​
 let file: String
 let method: String
 let line: Int
 let type: errorKind
}
​
func parse(_ source: String) throws -> Int {
 throw ParseError.init(
   file: "Users/Mccc/Log/Vip.swift",
   method: "LogMethod",
   line: 12,
   type: .InvalidFormat)
}

运用形式匹配来有条件地捕获过错。以下是怎么捕获函数抛出的任何过错:

do {
 let info = try parse("123")
 print(info)
} catch let e as ParseError {
 print("Parsing error: (e.type) [(e.file) : (e.method) : (e.line)]")
} catch {
 print("Other error: (error)")
}

处理Error的三种方法

  • 经过 try?疏忽Error
  • 经过 do - catch 捕捉 Error
  • 不捕捉 Error,在当时函数增加 throws 声明,Error 将主动抛给上层函数。假如最顶层函数(main 函数)依然没有捕捉 Error,那么程序将停止

相关的一些关键

rethrows & throws

throws关键字首先用在函数申明中,放在回来类型的前面,比方标准库中map的函数签名。

@frozen public struct Array<Element> {
 @inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
}

然后在函数内部,假如呈现或许的反常,就能够抛出反常。

enum NegativeError: Error {
 /// 负数
 case negative
}
​
let nums = [1, 2, 3, 4, -5]
​
do {
 let strNums = try nums.map { (num) throws -> String in
   if num >= 0 {
     return String(num)
   } else {
     throw NegativeError.negative
   }
 }
 print(strNums) // Will no print
} catch let err {
 print(err)
}
  • throws::在函数或许办法中抛出反常,让调用者有必要明确地处理或许的反常.
  • rethrows::自身并不抛出反常或许处理反常,其只起到传递反常的作用.
func methodThrows(num: Int) throws {
 if num < 0 {
   print("Throwing!")
   throw error
 }
 print("Executed!")
}
​
func methodRethrows(num: Int, f: (Int) throws -> ()) rethrows {
 try f(num)
}
​
do {
 try methodRethrows(num: 1, f: methodThrows)
} catch _ {
​
}

简略了解的话你能够将 rethrows 看做是 throws 的“子类”,rethrows 的办法能够用来重载那些被标为 throws 的办法或许参数,或许用来满意被标为 throws 的接口,可是反过来不行。

try / try!/ try? / defer

  • try: 和可选类型相似,编译器强制咱们在运用或许跑出过错的房时运用try关键字。需求和 do {} cathc {} 结合运用。
  • try!: 相似于可选型中的强制解包,同样不会对过错进行处理,可是一旦办法抛出过错,就会形成程序的崩溃
  • try?:有点相似于可选型中的可选链,假如办法正确,则完整履行;假如跑出过错,则办法提早完毕,但不会抛出过错进行处理。
  • defer:将有必要履行的逻辑放在defer{}中,能够确保无论办法从哪个出口完毕,defer{}中的代码都会履行,一般会将 defer{ } 放在办法体的最上方,defer代码段总是在办法生命周期的最后才履行

fatalError

无条件地打印给定的音讯并停止履行。

fatalError("something wrong")

fatalError的存在含义是:

在调试时咱们能够运用断语来排除相似这样的问题,可是断语只会在 Debug 环境中有用,而在 Release 编译中一切的断语都将被禁用。在遇到的确由于输入的过错无法使程序持续运转的时候,咱们一般考虑以产生丧命过错 fatalError 的方法来停止程序。

  • 父类中的某些办法,不想让他人调用,能够在办法中加上fatalError,这样子类假如想到用有必要重写
  • 关于其他全部咱们不希望他人随意调用,可是又不得不去完成的办法,咱们都应该运用 fatalError 来防止任何或许的误解。

Error相关的协议

声明一个轿车的结构体:

struct Car { }

界说一个轿车不能行进的过错。

extension Car {
 enum TroubleError: Error {
   /// 瘫痪:车辆无法行进
   case paralysis
   /// 油量缺乏
   case lackOilWarning
   /// 超员:减员之后能够持续行进。
   case overcrowding(Int)
 }
}

LocalizedError

描绘一个过错,该过错供给描绘过错产生原因的本地化音讯,并供给有关该过错的更多信息。

public protocol LocalizedError : Error {
​
 /// 供给描绘过错的本地化音讯
 var errorDescription: String? { get }
​
 /// 产生过错的原因
 var failureReason: String? { get }
​
 /// 怎么康复的提示
 var recoverySuggestion: String? { get }
​
 /// 其他帮助文本
 var helpAnchor: String? { get }
}
extension Car.TroubleError: LocalizedError {
 /// 供给描绘过错的本地化音讯
 var errorDescription: String? {
   switch self {
   case .paralysis:
     return NSLocalizedString("轿车现已瘫痪,无法行进", comment: "呼叫拖车维修")
   case .lackOilWarning:
     return NSLocalizedString("油量缺乏", comment: "清前往加油")
   case .overcrowding(let count):
     return NSLocalizedString("乘客超载,超载人数:(count)", comment: "超员部分乘客下车")
   }
 }
 
 /// 产生过错的原因
 var failureReason: String? {
   switch self {
   case .paralysis:
     return "轿车现已瘫痪,无法行进"
   case .lackOilWarning:
     return "油量缺乏"
   case .overcrowding(let count):
     return "乘客超载,超载人数:(count)"
   }
 }
 
 /// 怎么康复的提示
 var recoverySuggestion: String? {
   switch self {
   case .paralysis:
     return "寻找紧迫修车办法"
   case .lackOilWarning:
     return "去加油站加油"
   case .overcrowding(let count):
     return "把超载的人数(count)人赶下车"
   }
 }
 
 /// 其他帮助文本
 var helpAnchor: String? {
   switch self {
   case .paralysis:
     return "紧迫修车电话:0632-2347232"
   case .lackOilWarning:
     return "地图搜索加油站"
   case .overcrowding(_):
     return "制止超载"
   }
 }
}

CustomNSError

Error ⇋ NSError

// 初始化一个NSError
let errorOC = NSError.init(domain: "intsig.qxb", code: 1000, userInfo: nil)
​
// 转化为Error
let swiftError = errorOC as Error
print(swiftError)
print(swiftError.localizedDescription)
​
// 转化为NSError
let error = swiftError as NSError

一直认为 NSError ⇋ Error ⇋ NSError 能够无障碍转化的。自从收到这个crash:

0 libswiftCore.dylib __swift_stdlib_bridgeErrorToNSError + 40
1 projectName loadDataDidFailed (文件名.swift:69)
​
...
...

在各个渠道也没找到详细原因。 只是主张运用CustomNSError来处理。 如有知道详细原因的同学,能够谈论回复一下。

描绘特定供给域、代码和用户信息字典的过错类型。

public protocol CustomNSError : Error {
​
 /// The domain of the error.
 static var errorDomain: String { get }
​
 /// The error code within the given domain.
 var errorCode: Int { get }
​
 /// The user-info dictionary.
 var errorUserInfo: [String : Any] { get }
}
extension Car.TroubleError: CustomNSError {
 static var errorDomain: String {
   return "Domain"
 }
 var errorCode: Int {
   switch self {
   case .paralysis:
     return 1000
   case .lackOilWarning:
     return 1001
   case .overcrowding(_):
     return 1002
   }
 }
 var errorUserInfo: [String : Any] {
   return [:]
 }
}

转成NSError

extension Car.TroubleError {
 func toNSError() -> NSError {
   NSError.init(domain: Car.TroubleError.errorDomain, code: errorCode, userInfo: errorUserInfo)
 }
}

RecoverableError

能够经过向用户供给几个潜在康复选项来康复的过错。这主要在运用 AppKit 的 macOS运用 中运用.

extension Car.TroubleError: RecoverableError {
 
 /// 在用户恳求时履行康复来康复的过错。这主要在运用 AppKit 的 macOS运用 中运用.
 func attemptRecovery(optionIndex recoveryOptionIndex: Int) -> Bool {
   if recoveryOptionIndex == 0 { // 呼叫紧迫车辆救援
     return false
   } else if recoveryOptionIndex == 1 { // 前往加油站加油
     return true
   } else if recoveryOptionIndex == 2 { // 处理超载状况
     return true
   }
   fatalError("something wrong")
 }
 
 /// 供给供给给用户的一组或许的康复选项
 var recoveryOptions: [String] {
   return ["呼叫紧迫车辆救援", "前往加油站加油", "处理超载状况"]
 }
}

KingfisherError

Kingfisher的过错封装很经典,是运用swift中enum的一个典型事例。读完这篇文章,必定能让咱们对swift的枚举和Error的运用有一个更深的了解,一起增加一些枚举的高级运用技巧。

英文原义:

Represents all the errors which can happen in Kingfisher framework. Kingfisher related methods always throw a KingfisherError or invoke the callback with KingfisherError as its error type. To handle errors from Kingfisher, you switch over the error to get a reason catalog, then switch over the reason to know error detail.

中文翻译:

KingfisherError 表明在 Kingfisher框架中或许产生的一切过错。与 Kingfisher 相关的办法总是抛出一个 KingfisherError 或许以 KingfisherError 作为过错类型调用回调。要处理来自 Kingfisher 的过错,需求 switch 过错以获取原因目录,然后 switch 原因了解过错细节。

public enum KingfisherError: Error {
 // 表明网络恳求阶段的过错原因
 case requestError(reason: RequestErrorReason)
 // 表明网络呼应阶段的过错原因
 case responseError(reason: ResponseErrorReason)
 // 表明 Kingfisher 缓存系统中的过错
 case cacheError(reason: CacheErrorReason)
 // 表明图画处理阶段的过错原因
 case processorError(reason: ProcessorErrorReason)
 // 表明在视图相关类中设置图画时呈现过错的原因
 case imageSettingError(reason: ImageSettingErrorReason)
}

相关值规划的五个枚举 RequestErrorReason,ResponseErrorReason,CacheErrorReason,ProcessorErrorReason,ImageSettingErrorReason

他们是界说在 KingfisherError 中独立的枚举,他们之间是包括和被包括的关系,了解这一点很重要,由于有了这种包括的办理,在运用中就需求经过KingfisherError.RequestErrorReason这种方法进行操作。

那么最重要的问题便是,怎么把上边5个独立的枚举进行串联呢?Kingfisher奇妙的地方就在这里,有5个独立的枚举,别离代表5大过错类型。也便是说这个框架肯定有这5大过错模块,咱们只需求给KingfisherError规划5个子选项,每个子选项相关上这5个独立枚举的值就ok了。

这个规划真的很奇妙,试想,假如把一切的过错都放到KingfisherError中,就显得十分冗余。咱们好好领会领会在swift下这么规划的妙用。

RequestErrorReason

英文原义:

Represents the error reason during networking request phase.

  • emptyRequest: The request is empty. Code 1001.

  • invalidURL: The URL of request is invalid. Code 1002.

  • taskCancelled: The downloading task is cancelled by user. Code 1003.

中文翻译:

表明网络恳求阶段的过错原因.

  • emptyRequest: 恳求为空。代码1001

  • invalidURL: 恳求的URL无效。代码1002

  • taskCancelled: 下载使命被用户取消。代码1003

public enum KingfisherError: Error {
 public enum RequestErrorReason {
   case emptyRequest
   case invalidURL(request: URLRequest)
   case taskCancelled(task: SessionDataTask, token: SessionDataTask.CancelToken)
 }
}

经过 RequestErrorReason 咱们能够很清楚的看出来这是一个恳求过错的原因。咱们留意reason这个词,在命名中,有或许没有这个词,表达的意境彻底不同,因而,Kingfisher 的牛逼就体现在这些细节之中。

RequestErrorReason 自身是一个枚举,一起它又被包括在 KingfisherError 中,这说明枚举之中能够有另一个枚举 。那么像这种状况咱们怎么运用呢?看下边的代码:

let reason = KingfisherError.RequestErrorReason.emptyRequest

枚举的拜访是一级一级进行的。咱们再看这行代码:case invalidURL(request: URLRequest)

并不是函数,它是枚举的一个一般的子选项(request: URLRequest)是它的一个相关值,相关于任何一个子选项,咱们都能够相关任何值,它的含义就在于,把这些值与子选项进行绑定,方便在需求的时候调用

ResponseErrorReason

英文原义:

Represents the error reason during networking response phase.

  • invalidURLResponse: The response is not a valid URL response. Code 2001.

  • invalidHTTPStatusCode: The response contains an invalid HTTP status code. Code 2002.

  • URLSessionError: An error happens in the system URL session. Code 2003.

  • dataModifyingFailed: Data modifying fails on returning a valid data. Code 2004.

  • noURLResponse: The task is done but no URL response found. Code 2005.

中文翻译:

表明网络呼应阶段的过错原因。

  • invalidURLResponse: 该呼应不是有用的URL呼应。代码2001。

  • invalidHTTPStatusCode: 呼应包括无效的HTTP状态码。代码2002。

  • URLSessionError: 统URL会话中产生过错。代码2003。

  • dataModifyingFailed: 回来有用数据时数据修正失利。代码2004。

  • noURLResponse: 使命完结但没有找到URL呼应。代码2005。

public enum KingfisherError: Error {
 public enum ResponseErrorReason {
   case invalidURLResponse(response: URLResponse)
   case invalidHTTPStatusCode(response: HTTPURLResponse)
   case URLSessionError(error: Error)
   case dataModifyingFailed(task: SessionDataTask)
   case noURLResponse(task: SessionDataTask)
 }
}

CacheErrorReason

英文原义:

Represents the error reason during Kingfisher caching system.

  • fileEnumeratorCreationFailed: Cannot create a file enumerator for a certain disk URL. Code 3001.
  • invalidFileEnumeratorContent: Cannot get correct file contents from a file enumerator. Code 3002.
  • invalidURLResource: The file at target URL exists, but its URL resource is unavailable. Code 3003.
  • cannotLoadDataFromDisk: The file at target URL exists, but the data cannot be loaded from it. Code 3004.
  • cannotCreateDirectory: Cannot create a folder at a given path. Code 3005.
  • imageNotExisting: The requested image does not exist in cache. Code 3006.
  • cannotConvertToData: Cannot convert an object to data for storing. Code 3007.
  • cannotSerializeImage: Cannot serialize an image to data for storing. Code 3008.
  • cannotCreateCacheFile: Cannot create the cache file at a certain fileURL under a key. Code 3009.
  • cannotSetCacheFileAttribute: Cannot set file attributes to a cached file. Code 3010.

中文翻译:

在 Kingfisher 缓存系统中呈现的过错。

  • fileEnumeratorCreationFailed: 无法为某个磁盘URL创立文件枚举器。代码3001

  • invalidFileEnumeratorContent: 无法从文件枚举器获取正确的文件内容。代码3002

  • invalidURLResource: 方针URL上的文件存在,可是它的URL资源不可用。代码3003

  • cannotLoadDataFromDisk: 方针URL上的文件存在,但无法从中加载数据。代码3004

  • cannotCreateDirectory: 无法在给定途径上创立文件夹。代码3005

  • imageNotExisting: 缓存中不存在所恳求的图片。代码3006

  • cannotConvertToData: 无法将对象转化为用于存储的数据。代码3007

  • cannotSerializeImage: 无法将图片序列化为要存储的数据。代码3008

  • cannotCreateCacheFile: 无法在某个键下的某个文件上创立缓存文件。代码3009

  • cannotSetCacheFileAttribute: Cannot set file attributes to a cached file. Code 3010

public enum KingfisherError: Error {
 public enum CacheErrorReason {
   case fileEnumeratorCreationFailed(url: URL)
   case invalidFileEnumeratorContent(url: URL)
   case invalidURLResource(error: Error, key: String, url: URL)
   case cannotLoadDataFromDisk(url: URL, error: Error)
   case cannotCreateDirectory(path: String, error: Error)
   case imageNotExisting(key: String)
   case cannotConvertToData(object: Any, error: Error)
   case cannotSerializeImage(image: KFCrossPlatformImage?, original: Data?, serializer: CacheSerializer)
   case cannotCreateCacheFile(fileURL: URL, key: String, data: Data, error: Error)
   case cannotSetCacheFileAttribute(filePath: String, attributes: [FileAttributeKey : Any], error: Error)
 }
}

ProcessorErrorReason

英文原义:

Represents the error reason during image processing phase.

  • processingFailed: Image processing fails. There is no valid output image from the processor. Code 4001.

中文翻译:

代表在图片处理阶段的过错原因。

  • processingFailed: 图画处理失利。处理器没有有用的输出图画。代码4001
public enum KingfisherError: Error {
 public enum ProcessorErrorReason {
   case processingFailed(processor: ImageProcessor, item: ImageProcessItem)
 }
}

ImageSettingErrorReason

英文原义:

Represents the error reason during image setting in a view related class.

  • emptySource: The input resource is empty or nil. Code 5001.

  • notCurrentSourceTask: The source task is finished, but it is not the one expected now. Code 5002.

  • dataProviderError: An error happens during getting data from an ImageDataProvider. Code 5003.

  • alternativeSourcesExhausted: No more alternative Source can be used in current loading process. Code 5004

中文翻译:

表明在视图相关类中设置图画时呈现过错的原因。

  • emptySource: 输入资源为空或“nil”。代码5001

  • notCurrentSourceTask: 源使命现已完结,但不是现在所期望的使命。代码5002

  • dataProviderError: 从 ImageDataProvider 获取数据时产生过错。代码5003

  • alternativeSourcesExhausted: 在当时加载进程中不能运用更多的代替“源”。它的意思是。运用了 alternativeSourcesKingfisher 测验从开始的过错康复,但运用一切给定的代替源依然失利。相关的值包括加载进程中遇到的一切过错,包括原始源加载过错和一切代替源过错。

public enum KingfisherError: Error {
 public enum ImageSettingErrorReason {
   case emptySource
   case notCurrentSourceTask(result: RetrieveImageResult?, error: Error?, source: Source)
   case dataProviderError(provider: ImageDataProvider, error: Error)
   case alternativeSourcesExhausted([PropagationError])
 }
}

快捷的检验办法&属性

是否使命被取消

public var isTaskCancelled: Bool {
 if case .requestError(reason: .taskCancelled) = self {
   return true
 }
 return false
}

是否无效的回来状态码

ResponseErrorReason.invalidHTTPStatusCode 这个case,并且相关的code与给定的共同。

public func isInvalidResponseStatusCode(_ code: Int) -> Bool {
 if case .responseError(reason: .invalidHTTPStatusCode(let response)) = self {
   return response.statusCode == code
 }
 return false
}

是否无效呼应状态码

public var isInvalidResponseStatusCode: Bool {
 if case .responseError(reason: .invalidHTTPStatusCode) = self {
   return true
 }
 return false
}

是否当时使命

查看是否为 ImageSettingErrorReason.notCurrentSourceTask 类型过错。当旧的图画设置使命仍在运转而新的图画设置使命启动时,将设置新的使命标识符并掩盖旧的使命。当旧的使命完毕时,一个 .notCurrentSourceTask 过错将会被抛出,让您知道设置进程以必定的成果完毕,可是 image view or button 没有被设置。

public var isNotCurrentTask: Bool {
 if case .imageSettingError(reason: .notCurrentSourceTask(_, _, _)) = self {
   return true
 }
 return false
}

localized message describing

在开发中,假如程序遇到过错,咱们往往会给用户展示更加直观的信息,这就要求咱们把过错信息转化成易于了解的内容。因而咱们只需完成LocalizedError协议就好了。

extension KingfisherError: LocalizedError {  
 public var errorDescription: String? {
   switch self {
   case .requestError(let reason): return reason.errorDescription
   case .responseError(let reason): return reason.errorDescription
   case .cacheError(let reason): return reason.errorDescription
   case .processorError(let reason): return reason.errorDescription
   case .imageSettingError(let reason): return reason.errorDescription
   }
 }
}
​
extension KingfisherError: CustomNSError {
 public var errorCode: Int {
   switch self {
   case .requestError(let reason): return reason.errorCode
   case .responseError(let reason): return reason.errorCode
   case .cacheError(let reason): return reason.errorCode
   case .processorError(let reason): return reason.errorCode
   case .imageSettingError(let reason): return reason.errorCode
   }
 }
}

经过扩展给五大过错枚举,增加描绘。

extension KingfisherError.RequestErrorReason {
 var errorDescription: String? {
   switch self {
   case .emptyRequest:
     return "The request is empty or `nil`."
   case .invalidURL(let request):
     return "The request contains an invalid or empty URL. Request: (request)."
   case .taskCancelled(let task, let token):
     return "The session task was cancelled. Task: (task), cancel token: (token)."
   }
 }
 
 var errorCode: Int {
   switch self {
   case .emptyRequest: return 1001
   case .invalidURL: return 1002
   case .taskCancelled: return 1003
   }
 }
}
​
extension KingfisherError.ResponseErrorReason {
 var errorDescription: String? {
   switch self {
   case .invalidURLResponse(let response):
     return "The URL response is invalid: (response)"
   case .invalidHTTPStatusCode(let response):
     return "The HTTP status code in response is invalid. Code: (response.statusCode), response: (response)."
   case .URLSessionError(let error):
     return "A URL session error happened. The underlying error: (error)"
   case .dataModifyingFailed(let task):
     return "The data modifying delegate returned `nil` for the downloaded data. Task: (task)."
   case .noURLResponse(let task):
     return "No URL response received. Task: (task),"
   }
 }
 
 var errorCode: Int {
   switch self {
   case .invalidURLResponse: return 2001
   case .invalidHTTPStatusCode: return 2002
   case .URLSessionError: return 2003
   case .dataModifyingFailed: return 2004
   case .noURLResponse: return 2005
   }
 }
}
​
extension KingfisherError.CacheErrorReason {
 var errorDescription: String? {
   switch self {
   case .fileEnumeratorCreationFailed(let url):
     return "Cannot create file enumerator for URL: (url)."
   case .invalidFileEnumeratorContent(let url):
     return "Cannot get contents from the file enumerator at URL: (url)."
   case .invalidURLResource(let error, let key, let url):
     return "Cannot get URL resource values or data for the given URL: (url). " +
         "Cache key: (key). Underlying error: (error)"
   case .cannotLoadDataFromDisk(let url, let error):
     return "Cannot load data from disk at URL: (url). Underlying error: (error)"
   case .cannotCreateDirectory(let path, let error):
     return "Cannot create directory at given path: Path: (path). Underlying error: (error)"
   case .imageNotExisting(let key):
     return "The image is not in cache, but you requires it should only be " +
         "from cache by enabling the `.onlyFromCache` option. Key: (key)."
   case .cannotConvertToData(let object, let error):
     return "Cannot convert the input object to a `Data` object when storing it to disk cache. " +
         "Object: (object). Underlying error: (error)"
   case .cannotSerializeImage(let image, let originalData, let serializer):
     return "Cannot serialize an image due to the cache serializer returning `nil`. " +
         "Image: (String(describing:image)), original data: (String(describing: originalData)), " +
         "serializer: (serializer)."
   case .cannotCreateCacheFile(let fileURL, let key, let data, let error):
     return "Cannot create cache file at url: (fileURL), key: (key), data length: (data.count). " +
         "Underlying foundation error: (error)."
   case .cannotSetCacheFileAttribute(let filePath, let attributes, let error):
     return "Cannot set file attribute for the cache file at path: (filePath), attributes: (attributes)." +
         "Underlying foundation error: (error)."
   }
 }
 
 var errorCode: Int {
   switch self {
   case .fileEnumeratorCreationFailed: return 3001
   case .invalidFileEnumeratorContent: return 3002
   case .invalidURLResource: return 3003
   case .cannotLoadDataFromDisk: return 3004
   case .cannotCreateDirectory: return 3005
   case .imageNotExisting: return 3006
   case .cannotConvertToData: return 3007
   case .cannotSerializeImage: return 3008
   case .cannotCreateCacheFile: return 3009
   case .cannotSetCacheFileAttribute: return 3010
   }
 }
}
​
extension KingfisherError.ProcessorErrorReason {
 var errorDescription: String? {
   switch self {
   case .processingFailed(let processor, let item):
     return "Processing image failed. Processor: (processor). Processing item: (item)."
   }
 }
 
 var errorCode: Int {
   switch self {
   case .processingFailed: return 4001
   }
 }
}
​
extension KingfisherError.ImageSettingErrorReason {
 var errorDescription: String? {
   switch self {
   case .emptySource:
     return "The input resource is empty."
   case .notCurrentSourceTask(let result, let error, let resource):
     if let result = result {
       return "Retrieving resource succeeded, but this source is " +
           "not the one currently expected. Result: (result). Resource: (resource)."
     } else if let error = error {
       return "Retrieving resource failed, and this resource is " +
           "not the one currently expected. Error: (error). Resource: (resource)."
     } else {
       return nil
     }
   case .dataProviderError(let provider, let error):
     return "Image data provider fails to provide data. Provider: (provider), error: (error)"
   case .alternativeSourcesExhausted(let errors):
     return "Image setting from alternaive sources failed: (errors)"
   }
 }
 
 var errorCode: Int {
   switch self {
   case .emptySource: return 5001
   case .notCurrentSourceTask: return 5002
   case .dataProviderError: return 5003
   case .alternativeSourcesExhausted: return 5004
   }
 }
}