河枫 發表於 2022-11-30 09:29:06

Swift Error重构的基础示例详解

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>Error</li><ul class="second_class_ul"><li>定义</li><li>概述</li><li>用枚举来表示简单的错误</li><li>用结构体或其他类型表示复杂的错误</li><li>处理Error的三种方式</li></ul><li>相关的一些关键词</li><ul class="second_class_ul"><li>rethrows &amp; throws</li><li>try / try!/ try? / defer</li><li>fatalError</li></ul><li>Error相关的协议</li><ul class="second_class_ul"><li>LocalizedError</li><li>CustomNSError</li><ul class="third_class_ul"><li>转成NSError</li></ul><li>RecoverableError</li><ul class="third_class_ul"></ul><li>KingfisherError</li><ul class="third_class_ul"></ul><li>RequestErrorReason</li><ul class="third_class_ul"></ul><li>ResponseErrorReason</li><ul class="third_class_ul"></ul><li>CacheErrorReason</li><ul class="third_class_ul"></ul><li>ProcessorErrorReason</li><ul class="third_class_ul"></ul><li>ImageSettingErrorReason</li><ul class="third_class_ul"></ul><li>便捷的检验方法&amp;属性</li><ul class="third_class_ul"></ul><li>localized message describing</li><ul class="third_class_ul"></ul></ul></ul></div><p class="maodian"></p><h2>Error</h2>
<p>在开发中,往往最容易被忽略的内容就是对错误的处理。有经验的开发者,能够对自己写的每行代码负责,而且非常清楚自己写的代码在什么时候会出现异常,这样就能提前做好错误处理。</p>
<div class="cros igoods"><div class="goodsin" data-img="https://img14.360buyimg.com/pop/jfs/t892/37/1194674553/190004/7f3db189/55837601Nf4df3891.jpg" data-name="Swift基础教程(图灵出品)" data-owner="京东自营" data-price="42.6" data-tgid="38" data-url="https://union-click.jd.com/jdc?e=&amp;p=JF8BAMkJK1olXwUCVFxaDE4XBV8IG10VXQQHUm4ZVxNJXF9RXh5UHw0cSgYYXBcIWDoXSQVJQwYCUl5dCk4RHDZNRwYlB112Jy4GfQp3AAlLGQ0PWBh9AT0EeEcbM2gNHF4dXwMBZF5eDkwXAmoIK2sVXDZQOobrvpOysnPcsdTA1ZEyVW5dD0wfBWkKHFgQVQQEZF5VDHtUVypcWBhdbTYyV25tOEsnAF9WdVpGWwULUVcOZhZEAW4KWxlDMwAHVF1eCUMWM20JGlkXbTY"></div></div>
<p class="maodian"></p><h3>定义</h3>
<p>Swift 里面的Error是一个协议</p>
<div class="jb51code"><pre class="brush:cpp;">public protocol Error : Sendable { }
</pre></div>
<p>Sendable: 可以安全地将可发送类型的值从一个并发域传递到另一个&mdash;&mdash;例如,您可以在调用参与者的方法时将可发送值作为参数传递</p>
<p class="maodian"></p><h3>概述</h3>
<blockquote><p>Any type that declares conformance to the Error protocol can be used to represent an error in Swift&rsquo;s error handling system. Because the Error protocol has no requirements of its own, you can declare conformance on any custom type you create.</p></blockquote>
<p>在Swift的错误处理系统中,任何声明符合Error协议的类型都可以用来表示错误。因为Error协议没有它自己的要求,所以您可以对您创建的任何自定义类型声明一致性。</p>
<p class="maodian"></p><h3>用枚举来表示简单的错误</h3>
<p>Swift 的枚举非常适合表示简单的错误。创建一个符合<code>Error</code>协议的枚举,并为每个可能的错误提供一个case。如果需要有关错误的其他详细信息,可以使用关联值来包含该信息。</p>
<div class="jb51code"><pre class="brush:cpp;">/// 声明一个Int解析的Error
enum IntParsingError: Error {
/// 超过长度
case overflow
/// 无法解析的字符
case invalidInput(String)
/// 其他错误类型
case other
}
</pre></div>
<div class="jb51code"><pre class="brush:cpp;">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() -&gt; Int? {
      let str = String(self)
      if let int = Int(str) {
          return int
      }
      return nil
}
}
extension String {
public func toInt() -&gt; Int? {
      if let num = NumberFormatter().number(from: self) {
          return num.intValue
      } else {
          return nil
      }
}
}
</pre></div>
<div class="jb51code"><pre class="brush:cpp;">let money: String = "100块钱"
let a = try? Int(validating: money)
</pre></div>
<p>此时的 a 是一个Int?类型, 初始化失败就返回nil.</p>
<div class="jb51code"><pre class="brush:cpp;">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")
}
</pre></div>
<p>此时的price是一个Int类型, 如果转换失败,就会抛出异常。</p>
<p class="maodian"></p><h3>用结构体或其他类型表示复杂的错误</h3>
<p>以下示例在解析时,使用结构来表示错误,包括发生错误的文件,方法和行号:</p>
<div class="jb51code"><pre class="brush:cpp;">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 -&gt; Int {
throw ParseError.init(
      file: "Users/Mccc/Log/Vip.swift",
      method: "LogMethod",
      line: 12,
      type: .InvalidFormat)
}
</pre></div>
<p>使用模式匹配来有条件地捕获错误。以下是如何捕获函数抛出的任何错误:</p>
<div class="jb51code"><pre class="brush:cpp;">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)")
}
</pre></div>
<p class="maodian"></p><h3>处理Error的三种方式</h3>
<ul><li>通过 try?忽略Error</li><li>通过 <code>do - catch</code> 捕捉 <code>Error</code></li><li>不捕捉 <code>Error</code>,在当前函数增加 <code>throws</code> 声明,<code>Error</code> 将自动抛给上层函数。如果最顶层函数(main 函数)依然没有捕捉 <code>Error</code>,那么程序将终止</li></ul>
<p class="maodian"></p><h2>相关的一些关键词</h2>
<p class="maodian"></p><h3>rethrows &amp; throws</h3>
<p><code>throws</code>关键字首先用在函数申明中,放在返回类型的前面,比如标准库中map的函数签名。</p>
<div class="jb51code"><pre class="brush:cpp;">@frozen public struct Array&lt;Element&gt; {
@inlinable public func map&lt;T&gt;(_ transform: (Element) throws -&gt; T) rethrows -&gt;
}
</pre></div>
<p>然后在函数内部,如果出现可能的异常,就可以抛出异常。</p>
<div class="jb51code"><pre class="brush:cpp;">enum NegativeError: Error {
/// 负数
case negative
}
let nums =
do {
let strNums = try nums.map { (num) throws -&gt; String in
      if num &gt;= 0 {
          return String(num)
      } else {
          throw NegativeError.negative
      }
}
print(strNums) // Will no print
} catch let err {
print(err)
}
</pre></div>
<ul><li>throws::在函数或者方法中抛出异常,让调用者必须明确地处理可能的异常.</li><li>rethrows::本身并不抛出异常或者处理异常,其只起到传递异常的作用.</li></ul>
<div class="jb51code"><pre class="brush:cpp;">func methodThrows(num: Int) throws {
if num &lt; 0 {
      print("Throwing!")
      throw error
}
print("Executed!")
}
func methodRethrows(num: Int, f: (Int) throws -&gt; ()) rethrows {
try f(num)
}
do {
try methodRethrows(num: 1, f: methodThrows)
} catch _ {
}
</pre></div>
<p>简单理解的话你可以将 <code>rethrows</code> 看做是 <code>throws</code> 的&ldquo;子类&rdquo;,<code>rethrows</code> 的方法可以用来重载那些被标为 <code>throws</code> 的方法或者参数,或者用来满足被标为 <code>throws</code> 的接口,但是反过来不行。</p>
<p class="maodian"></p><h3>try / try!/ try? / defer</h3>
<ul><li>try: 和可选类型相似,编译器强制我们在使用可能跑出错误的房时使用try关键字。需要和 <code>do {} cathc {}</code> 结合使用。</li><li>try!: 类似于可选型中的强制解包,同样不会对错误进行处理,但是一旦方法抛出错误,就会造成程序的崩溃</li><li>try?:有点类似于可选型中的可选链,如果方法正确,则完整执行;如果跑出错误,则方法提前结束,但不会抛出错误进行处理。</li><li>defer:将必须执行的逻辑放在defer{}中,可以保证无论方法从哪个出口结束,defer{}中的代码都会执行,通常会将 defer{ } 放在方法体的最上方,<strong>defer代码段总是在方法生命周期的最后才执行</strong>。</li></ul>
<p class="maodian"></p><h3>fatalError</h3>
<p>无条件地打印给定的消息并停止执行。</p>
<div class="jb51code"><pre class="brush:cpp;">fatalError("something wrong")
</pre></div>
<p>fatalError的存在意义是:</p>
<p>在调试时我们可以使用断言来排除类似这样的问题,但是断言只会在 Debug 环境中有效,而在 <strong>Release 编译中所有的断言都将被禁用</strong>。在遇到确实因为输入的错误无法使程序继续运行的时候,我们一般考虑以产生致命错误 fatalError 的方式来终止程序。</p>
<ul><li>父类中的某些方法,不想让别人调用,可以在方法中加上fatalError,这样子类如果想到用必须重写</li><li>对于其他一切我们不希望别人随意调用,但是又不得不去实现的方法,我们都应该使用 fatalError 来避免任何可能的误会。</li></ul>
<p class="maodian"></p><h2>Error相关的协议</h2>
<p>声明一个汽车的结构体:</p>
<div class="jb51code"><pre class="brush:cpp;">struct Car { }
</pre></div>
<p>定义一个汽车不能行驶的错误。</p>
<div class="jb51code"><pre class="brush:cpp;">extension Car {
enum TroubleError: Error {
      /// 瘫痪:车辆无法行驶
      case paralysis
      /// 油量不足
      case lackOilWarning
      /// 超员:减员之后可以继续行驶。
      case overcrowding(Int)
}
}
</pre></div>
<p class="maodian"></p><h3>LocalizedError</h3>
<p>描述一个错误,该错误提供描述错误发生原因的本地化消息,并提供有关该错误的更多信息。</p>
<div class="jb51code"><pre class="brush:cpp;">public protocol LocalizedError : Error {
/// 提供描述错误的本地化消息
var errorDescription: String? { get }
/// 发生错误的原因
var failureReason: String? { get }
/// 如何恢复的提示
var recoverySuggestion: String? { get }
/// 其他帮助文本
var helpAnchor: String? { get }
}
</pre></div>
<div class="jb51code"><pre class="brush:cpp;">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 "禁止超载"
      }
}
}
</pre></div>
<p class="maodian"></p><h3>CustomNSError</h3>
<p>Error ⇋ NSError</p>
<div class="jb51code"><pre class="brush:cpp;">// 初始化一个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
</pre></div>
<p>一直认为 NSError ⇋ Error ⇋ NSError 可以无障碍转换的。自从收到这个crash:</p>
<div class="jb51code"><pre class="brush:cpp;">0 libswiftCore.dylib __swift_stdlib_bridgeErrorToNSError + 40
1 projectName loadDataDidFailed (文件名.swift:69)
...
...
</pre></div>
<p>在各个渠道也没找到具体原因。 只是建议使用CustomNSError来处理。 如有知道具体原因的同学,可以评论回复一下。</p>
<p>描述特定提供域、代码和用户信息字典的错误类型。</p>
<div class="jb51code"><pre class="brush:cpp;">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: { get }
}
</pre></div>
<div class="jb51code"><pre class="brush:cpp;">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: {
      return [:]
}
}
</pre></div>
<p class="maodian"></p><h4>转成NSError</h4>
<div class="jb51code"><pre class="brush:cpp;">extension Car.TroubleError {
func toNSError() -&gt; NSError {
      NSError.init(domain: Car.TroubleError.errorDomain, code: errorCode, userInfo: errorUserInfo)
}
}
</pre></div>
<p class="maodian"></p><h3>RecoverableError</h3>
<p>可以通过向用户提供几个潜在恢复选项来恢复的错误。这主要在使用 AppKit 的 macOS应用 中使用.</p>
<div class="jb51code"><pre class="brush:cpp;">extension Car.TroubleError: RecoverableError {
/// 在用户请求时执行恢复来恢复的错误。这主要在使用 AppKit 的 macOS应用 中使用.
func attemptRecovery(optionIndex recoveryOptionIndex: Int) -&gt; Bool {
      if recoveryOptionIndex == 0 { // 呼叫紧急车辆救援
          return false
      } else if recoveryOptionIndex == 1 { // 前往加油站加油
          return true
      } else if recoveryOptionIndex == 2 { // 处理超载情况
          return true
      }
      fatalError("something wrong")
}
/// 提供提供给用户的一组可能的恢复选项
var recoveryOptions: {
      return ["呼叫紧急车辆救援", "前往加油站加油", "处理超载情况"]
}
}
</pre></div>
<p class="maodian"></p><h3>KingfisherError</h3>
<p>Kingfisher的错误封装很经典,是使用swift中enum的一个典型案例。读完这篇文章,一定能让大家对swift的枚举和Error的使用有一个更深的理解,同时增加一些枚举的高级使用技巧。</p>
<p>英文原义:</p>
<blockquote><p>Represents all the errors which can happen in Kingfisher framework. Kingfisher related methods always throw a <code>KingfisherError</code> or invoke the callback with <code>KingfisherError</code> 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.</p></blockquote>
<p>中文翻译:</p>
<p><strong>KingfisherError</strong> 表示在 Kingfisher框架中可能发生的所有错误。与 <strong>Kingfisher</strong> 相关的方法总是抛出一个 <strong>KingfisherError</strong> 或者以 <strong>KingfisherError</strong> 作为错误类型调用回调。要处理来自 <strong>Kingfisher</strong> 的错误,需要 switch 错误以获取原因目录,然后 switch 原因了解错误细节。</p>
<div class="jb51code"><pre class="brush:cpp;">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)
}
</pre></div>
<p>关联值设计的五个枚举 RequestErrorReason,ResponseErrorReason,CacheErrorReason,ProcessorErrorReason,ImageSettingErrorReason</p>
<p>他们是定义在 <strong>KingfisherError</strong> 中独立的枚举,他们之间是包含和被包含的关系,理解这一点很重要,因为有了这种包含的管理,在使用中就需要通过<code>KingfisherError.RequestErrorReason</code>这种方式进行操作。</p>
<p>那么最重要的问题就是,如何把上边5个独立的枚举进行串联呢?Kingfisher巧妙的地方就在这里,有5个独立的枚举,分别代表5大错误类型。也就是说这个框架肯定有这5大错误模块,我们只需要给KingfisherError设计5个子选项,每个子选项关联上这5个独立枚举的值就ok了。</p>
<p>这个设计真的很巧妙,试想,如果把所有的错误都放到KingfisherError中,就显得非常冗余。大家好好体会体会在swift下这么设计的妙用。</p>
<p class="maodian"></p><h3>RequestErrorReason</h3>
<p>英文原义:</p>
<blockquote><p>Represents the error reason during networking request phase.</p></blockquote>
<blockquote><p>emptyRequest: The request is empty. Code 1001.</p>
<p>invalidURL: The URL of request is invalid. Code 1002.</p>
<p>taskCancelled: The downloading task is cancelled by user. Code 1003.</p></blockquote>
<p>中文翻译:</p>
<p>表示网络请求阶段的错误原因.</p>
<blockquote><p>emptyRequest: 请求为空。代码1001</p>
<p>invalidURL: 请求的URL无效。代码1002</p>
<p>taskCancelled: 下载任务被用户取消。代码1003</p></blockquote>
<div class="jb51code"><pre class="brush:cpp;">public enum KingfisherError: Error {
public enum RequestErrorReason {
      case emptyRequest
      case invalidURL(request: URLRequest)
      case taskCancelled(task: SessionDataTask, token: SessionDataTask.CancelToken)
}
}
</pre></div>
<p>通过 <code>RequestErrorReason</code> 我们能够很清楚的看出来这是一个请求错误的原因。大家注意<code>reason</code>这个词,在命名中,有或者没有这个词,表达的意境完全不同,因此,Kingfisher 的厉害就体现在这些细节之中。</p>
<p><strong>RequestErrorReason</strong> 本身是一个枚举,同时它又被包含在 <strong>KingfisherError</strong> 中,<strong>这说明枚举之中可以有另一个枚举</strong> 。那么像这种情况我们怎么使用呢?看下边的代码:</p>
<div class="jb51code"><pre class="brush:cpp;">let reason = KingfisherError.RequestErrorReason.emptyRequest
</pre></div>
<p>枚举的访问是一级一级进行的。我们再看这行代码:<code>case invalidURL(request: URLRequest)</code></p>
<p>并不是函数,它是枚举的一个普通的子选项。<code>(request: URLRequest)</code>是它的一个关联值,相对于任何一个子选项,我们都可以关联任何值,它的意义就在于,把这些值与子选项进行绑定,方便在需要的时候调用。</p>
<p class="maodian"></p><h3>ResponseErrorReason</h3>
<p>英文原义:</p>
<blockquote><p>Represents the error reason during networking response phase.</p></blockquote>
<blockquote><p>invalidURLResponse: The response is not a valid URL response. Code 2001.</p>
<p>invalidHTTPStatusCode: The response contains an invalid HTTP status code. Code 2002.</p>
<p>URLSessionError: An error happens in the system URL session. Code 2003.</p>
<p>dataModifyingFailed: Data modifying fails on returning a valid data. Code 2004.</p>
<p>noURLResponse: The task is done but no URL response found. Code 2005.</p></blockquote>
<p>中文翻译:</p>
<p>表示网络响应阶段的错误原因。</p>
<blockquote><p>invalidURLResponse: 该响应不是有效的URL响应。代码2001。</p>
<p>invalidHTTPStatusCode: 响应包含无效的HTTP状态码。代码2002。</p>
<p>URLSessionError: 统URL会话中发生错误。代码2003。</p>
<p>dataModifyingFailed: 返回有效数据时数据修改失败。代码2004。</p>
<p>noURLResponse: 任务完成但没有找到URL响应。代码2005。</p></blockquote>
<div class="jb51code"><pre class="brush:cpp;">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)
}
}
</pre></div>
<p class="maodian"></p><h3>CacheErrorReason</h3>
<p>英文原义:</p>
<blockquote><p>Represents the error reason during Kingfisher caching system.</p></blockquote>
<blockquote><p>fileEnumeratorCreationFailed: Cannot create a file enumerator for a certain disk URL. Code 3001.</p>
<p>invalidFileEnumeratorContent: Cannot get correct file contents from a file enumerator. Code 3002.</p>
<p>invalidURLResource: The file at target URL exists, but its URL resource is unavailable. Code 3003.</p>
<p>cannotLoadDataFromDisk: The file at target URL exists, but the data cannot be loaded from it. Code 3004.</p>
<p>cannotCreateDirectory: Cannot create a folder at a given path. Code 3005.</p>
<p>imageNotExisting: The requested image does not exist in cache. Code 3006.</p>
<p>cannotConvertToData: Cannot convert an object to data for storing. Code 3007.</p>
<p>cannotSerializeImage: Cannot serialize an image to data for storing. Code 3008.</p>
<p>cannotCreateCacheFile: Cannot create the cache file at a certain fileURL under a key. Code 3009.</p>
<p>cannotSetCacheFileAttribute: Cannot set file attributes to a cached file. Code 3010.</p></blockquote>
<p>中文翻译:</p>
<p>在 Kingfisher 缓存系统中出现的错误。</p>
<blockquote><p>fileEnumeratorCreationFailed: 无法为某个磁盘URL创建文件枚举器。代码3001</p>
<p>invalidFileEnumeratorContent: 无法从文件枚举器获取正确的文件内容。代码3002</p>
<p>invalidURLResource: 目标URL上的文件存在,但是它的URL资源不可用。代码3003</p>
<p>cannotLoadDataFromDisk: 目标URL上的文件存在,但无法从中加载数据。代码3004</p>
<p>cannotCreateDirectory: 无法在给定路径上创建文件夹。代码3005</p>
<p>imageNotExisting: 缓存中不存在所请求的图片。代码3006</p>
<p>cannotConvertToData: 无法将对象转换为用于存储的数据。代码3007</p>
<p>cannotSerializeImage: 无法将图片序列化为要存储的数据。代码3008</p>
<p>cannotCreateCacheFile: 无法在某个键下的某个文件上创建缓存文件。代码3009</p>
<p>cannotSetCacheFileAttribute: Cannot set file attributes to a cached file. Code 3010</p></blockquote>
<div class="jb51code"><pre class="brush:cpp;">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: , error: Error)
}
}
</pre></div>
<p class="maodian"></p><h3>ProcessorErrorReason</h3>
<p>英文原义:</p>
<blockquote><p>Represents the error reason during image processing phase.</p>
<p>processingFailed: Image processing fails. There is no valid output image from the processor. Code 4001.</p></blockquote>
<p>中文翻译:</p>
<p>代表在图片处理阶段的错误原因。</p>
<p>processingFailed: 图像处理失败。处理器没有有效的输出图像。代码4001</p>
<div class="jb51code"><pre class="brush:cpp;">public enum KingfisherError: Error {
public enum ProcessorErrorReason {
      case processingFailed(processor: ImageProcessor, item: ImageProcessItem)
}
}
</pre></div>
<p class="maodian"></p><h3>ImageSettingErrorReason</h3>
<p>英文原义:</p>
<blockquote><p>Represents the error reason during image setting in a view related class.</p></blockquote>
<blockquote><p>emptySource: The input resource is empty or nil. Code 5001.</p>
<p>notCurrentSourceTask: The source task is finished, but it is not the one expected now. Code 5002.</p>
<p>dataProviderError: An error happens during getting data from an ImageDataProvider. Code 5003.</p>
<p>alternativeSourcesExhausted: No more alternative Source can be used in current loading process. Code 5004</p></blockquote>
<p>中文翻译:</p>
<p>表示在视图相关类中设置图像时出现错误的原因。</p>
<blockquote><p>emptySource: 输入资源为空或&ldquo;nil&rdquo;。代码5001</p>
<p>notCurrentSourceTask: 源任务已经完成,但不是现在所期望的任务。代码5002</p>
<p>dataProviderError: 从 ImageDataProvider 获取数据时发生错误。代码5003</p>
<p>alternativeSourcesExhausted: 在当前加载过程中不能使用更多的替代&ldquo;源&rdquo;。它的意思是。使用了 alternativeSources,<strong>Kingfisher</strong> 尝试从最初的错误恢复,但使用所有给定的替代源仍然失败。关联的值包含加载过程中遇到的所有错误,包括原始源加载错误和所有替代源错误。</p></blockquote>
<div class="jb51code"><pre class="brush:cpp;">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()
}
}
</pre></div>
<p class="maodian"></p><h3>便捷的检验方法&amp;属性</h3>
<p><strong>是否任务被取消</strong></p>
<div class="jb51code"><pre class="brush:cpp;">public var isTaskCancelled: Bool {
if case .requestError(reason: .taskCancelled) = self {
      return true
}
return false
}
</pre></div>
<p><strong>是否无效的返回状态码</strong></p>
<p>是<code>ResponseErrorReason.invalidHTTPStatusCode</code> 这个case,并且关联的code与给定的一致。</p>
<div class="jb51code"><pre class="brush:cpp;">public func isInvalidResponseStatusCode(_ code: Int) -&gt; Bool {
if case .responseError(reason: .invalidHTTPStatusCode(let response)) = self {
      return response.statusCode == code
}
return false
}
</pre></div>
<p><strong>是否无效响应状态码</strong></p>
<div class="jb51code"><pre class="brush:cpp;">public var isInvalidResponseStatusCode: Bool {
if case .responseError(reason: .invalidHTTPStatusCode) = self {
      return true
}
return false
}
</pre></div>
<p><strong>是否当前任务</strong></p>
<p>检查是否为 <code>ImageSettingErrorReason.notCurrentSourceTask</code> 类型错误。当旧的图像设置任务仍在运行而新的图像设置任务启动时,将设置新的任务标识符并覆盖旧的任务。当旧的任务结束时,一个 <code>.notCurrentSourceTask</code> 错误将会被抛出,让您知道设置过程以一定的结果结束,但是 <code>image view or button</code> 没有被设置。</p>
<div class="jb51code"><pre class="brush:cpp;">public var isNotCurrentTask: Bool {
if case .imageSettingError(reason: .notCurrentSourceTask(_, _, _)) = self {
      return true
}
return false
}
</pre></div>
<p class="maodian"></p><h3>localized message describing</h3>
<p>在开发中,如果程序遇到错误,我们往往会给用户展示更加直观的信息,这就要求我们把错误信息转换成易于理解的内容。因此我们只要实现LocalizedError协议就好了。</p>
<div class="jb51code"><pre class="brush:cpp;">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
      }
}
}
</pre></div>
<p>通过扩展给五大错误枚举,添加描述。</p>
<div class="jb51code"><pre class="brush:cpp;">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
      }
}
}</pre></div>
<p>以上就是Swift&nbsp;Error重构的基础示例详解的详细内容,更多关于Swift&nbsp;Error重构基础的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Swift 重构重载运算符示例解析</li><li>Swift Error重构优化详解</li><li>Swift重构自定义空等运算符&nbsp;“??=”&nbsp;实例</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Swift Error重构的基础示例详解