同步我的掘金MeR
??鶸學(xué)python看到錯誤處理這一章的時候,就想對比著swift來研究一下。
??本文前面是Error蘋果文檔的介紹,然后對Alamofire中的使用作簡單介紹作為實踐;后面還有關(guān)于fatalError和高級語言錯誤處理機制的理解
Error蘋果文檔
Swift中的Error長這個樣子
public protocol Error {
}
extension Error {
}
extension Error where Self.RawValue : SignedInteger {
}
extension Error where Self.RawValue : UnsignedInteger {
}
蘋果文檔對Error的介紹
A type representing an error value that can be thrown. 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.
??從介紹上可以看出遵守這個Error協(xié)議,就可以將錯誤拋出和捕獲,而且任何自定義的類型都可以遵守此Error協(xié)議。
??文檔中舉了兩個例子,第一個是自定義枚舉遵守Error來對Error進行分類,下面我們著重看下蘋果爸爸給出的第二個例子:用一個遵守Error協(xié)議的結(jié)構(gòu)體來獲取更多的錯誤信息。
struct XMLParsingError: Error {
enum ErrorKind {
case invalidCharacter
case mismatchedTag
case internalError
}
let line: Int
let column: Int
let kind: ErrorKind
}
func parse(_ source: String) throws -> XMLDoc {
// ...
throw XMLParsingError(line: 19, column: 5, kind: .mismatchedTag)
// ...
}
例子中創(chuàng)建了一個遵守Error協(xié)議的結(jié)構(gòu)體 XMLParsingError,帶有三個屬性來記錄解析XML文件時出錯的錯誤類型和行列信息。這樣在執(zhí)行parse方法的時候,就可以捕獲到可能的出現(xiàn)的錯誤信息
do {
let xmlDoc = try parse(myXMLData)
} catch let e as XMLParsingError {
print("Parsing error: \(e.kind) [\(e.line):\(e.column)]")
} catch {
print("Other error: \(error)")
}
// Prints "Parsing error: mismatchedTag [19:5]"
Alamofire中的錯誤示例
??刻意看了下Alamofire中的錯誤處理,它用單獨的一個文件AFError來管理使用中的錯誤,其中自定義了枚舉
public enum AFError: Error {
encoding process.
public enum ParameterEncodingFailureReason {
case missingURL
case jsonEncodingFailed(error: Error)
case propertyListEncodingFailed(error: Error)
}
public enum MultipartEncodingFailureReason {
case bodyPartURLInvalid(url: URL)
case bodyPartFilenameInvalid(in: URL)
case bodyPartFileNotReachable(at: URL)
case bodyPartFileNotReachableWithError(atURL: URL, error: Error)
case bodyPartFileIsDirectory(at: URL)
case bodyPartFileSizeNotAvailable(at: URL)
case bodyPartFileSizeQueryFailedWithError(forURL: URL, error: Error)
case bodyPartInputStreamCreationFailed(for: URL)
case outputStreamCreationFailed(for: URL)
case outputStreamFileAlreadyExists(at: URL)
case outputStreamURLInvalid(url: URL)
case outputStreamWriteFailed(error: Error)
case inputStreamReadFailed(error: Error)
}
public enum ResponseValidationFailureReason {
case dataFileNil
case dataFileReadFailed(at: URL)
case missingContentType(acceptableContentTypes: [String])
case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String)
case unacceptableStatusCode(code: Int)
}
public enum ResponseSerializationFailureReason {
case inputDataNil
case inputDataNilOrZeroLength
case inputFileNil
case inputFileReadFailed(at: URL)
case stringSerializationFailed(encoding: String.Encoding)
case jsonSerializationFailed(error: Error)
case propertyListSerializationFailed(error: Error)
}
case invalidURL(url: URLConvertible)
case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
case responseValidationFailed(reason: ResponseValidationFailureReason)
case responseSerializationFailed(reason: ResponseSerializationFailureReason)
}
以此將使用中出現(xiàn)的錯誤進行分類。
??不僅如此,作者還將AFError進行了擴展,增加部分判斷的方法,比如判斷是否是無效的URL:
// MARK: - Error Booleans
extension AFError {
/// Returns whether the AFError is an invalid URL error.
public var isInvalidURLError: Bool {
if case .invalidURL = self { return true }
return false
}
}
同時拓展中也遵守了LocalizedError協(xié)議來重寫errorDescription屬性,并且對已經(jīng)分類enum的自定義錯誤類型進行拓展重寫了屬性localizedDescription,這樣就構(gòu)成了項目中完整的錯誤處理機制。比如在Alamofire編碼解析方法方法encode中,有這么一段代碼
do {
let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options)
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = data
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
其中就是對于JSON序列化時,通過 do { try } catch 的方式來捕獲錯誤,如果捕獲到,將拋出已經(jīng)自定義好的 AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) 類型錯誤。
fatalError
還有一個錯誤相關(guān)的是 fatalError, 蘋果對其的描述:
Unconditionally prints a given message and stops execution.
定義:
/// - Parameters:
/// - message: The string to print. The default is an empty string.
/// - file: The file name to print with `message`. The default is the file
/// where `fatalError(_:file:line:)` is called.
/// - line: The line number to print along with `message`. The default is the
/// line number where `fatalError(_:file:line:)` is called.
public func fatalError(_ message: @autoclosure () -> String = default, file: StaticString = #file, line: UInt = #line) -> Never
這個我們估計最常見的地方就是在 init(coder: NSCoder) 中看到它,它的作用如蘋果描述的,無條件的打印出給定的信息同時終止程序。按照喵神的描述,fatalError的存在意義是:
<font size=3>在調(diào)試時我們可以使用斷言來排除類似這樣的問題,但是斷言只會在 Debug 環(huán)境中有效,而在 Release 編譯中所有的斷言都將被禁用。在遇到確實因為輸入的錯誤無法使程序繼續(xù)運行的時候,我們一般考慮以產(chǎn)生致命錯誤 fatalError 的方式來終止程序。</font>
而實際開發(fā)中喵神給出兩個使用的場景:
- 父類中的某些方法,不想讓別人調(diào)用,可以在方法中加上fatalError,這樣子類如果想到用必須重寫
- 對于其他一切我們不希望別人隨意調(diào)用,但是又不得不去實現(xiàn)的方法,我們都應(yīng)該使用 fatalError 來避免任何可能的誤會。
關(guān)于第二點就是我們常見到的在重寫UIView init(frame:) 時,Xcode會提示需要顯示重寫 init(coder: NSCoder),并且給出默認(rèn)實現(xiàn)的原因:
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
UIView遵守了協(xié)議 NSCoding,此協(xié)議要求實現(xiàn) init(coder: NSCoder)
public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
詳細(xì)的場景介紹請看喵神的文章。
關(guān)于錯誤處理機制
??鶸之前也想過錯誤處理機制的意義,返回一個狀態(tài)碼不就好了。比如我們打開一個文件,成功了返回一個整數(shù)的描述符,錯誤了就返回-1;或者我們請求聯(lián)網(wǎng),如果參數(shù)有問題,會和后臺約定一個數(shù)字作為錯誤碼,也都很好。然而只是單純的用錯誤碼會面臨兩個問題:
- 用錯誤碼來表示是否出錯十分不便,因為函數(shù)本身應(yīng)該返回的正常結(jié)果和錯誤碼混在一起,造成調(diào)用者必須用大量的代碼來判斷是否出錯。
- 一旦出錯,還要一級一級上報,直到某個函數(shù)可以處理該錯誤(比如,給用戶輸出一個錯誤信息)。
高級語言的錯誤處理機制就解決了這些問題。