Alamofire源碼解讀系列之錯誤處理(AFError)

 

1.正經常使用法

enum Movement {
    case Left
    case Right
    case Top
    case Bottom
}

let aMovement = Movement.Left

switch aMovement {
case .Left:
    print("left")
default:
    print("Unknow")
}

if case .Left = aMovement {
    print("Left")
}

if .Left == aMovement {
    print("Left")
}

2.聲明爲整型

enum Season: Int {
    case Spring = 0
    case Summer = 1
    case Autumn = 2
    case Winter = 3
}

3.聲明爲字符串類型

enum House: String {
    case ZhangSan = "I am zhangsan"
    case LiSi = "I am lisi"
}

let zs = House.ZhangSan
print(zs.rawValue)


enum CompassPoint: String {
case North, South, East, West
}

let n = CompassPoint.North
print(n.rawValue)

let s = CompassPoint(rawValue: "South");

4.聲明爲浮點類型

enum Constants: Double {
    case π = 3.14159
    case e = 2.71828
    case φ = 1.61803398874
    case λ = 1.30357
}

let pai = Constants.π
print(pai.rawValue)

5.其餘類型

enum VNodeFlags : UInt32 {
    case Delete = 0x00000001
    case Write = 0x00000002
    case Extended = 0x00000004
    case Attrib = 0x00000008
    case Link = 0x00000010
    case Rename = 0x00000020
    case Revoke = 0x00000040
    case None = 0x00000080
}

6.enum包含enum

enum Character {

    enum Weapon {
        case Bow
        case Sword
        case Lance
        case Dagger
    }

    enum Helmet {
        case Wooden
        case Iron
        case Diamond
    }

    case Thief
    case Warrior
    case Knight
}

let character = Character.Thief
let weapon = Character.Weapon.Bow
let helmet = Character.Helmet.Iron

7.結構體和枚舉

struct Scharacter {
    enum CharacterType {
        case Thief
        case Warrior
        case Knight
    }

    enum Weapon {
        case Bow
        case Sword
        case Lance
        case Dagger
    }

    let type: CharacterType
    let weapon: Weapon
}

let sc = Scharacter(type: .Thief, weapon: .Bow)
print(sc.type)

8.值關聯

enum Trade {
    case Buy(stock: String, amount: Int)
    case Sell(stock: String, amount: Int)
}

let trade = Trade.Buy(stock: "Car", amount: 100)
if case let Trade.Buy(stock, amount) = trade {
    print("buy \(amount) of \(stock)")
}

enum Trade0 {
    case Buy(String, Int)
    case Sell(String, Int)
}

let trade0 = Trade0.Buy("Car0", 100)
if case let Trade0.Buy(stock, amount) = trade0 {
    print("buy \(amount) of \(stock)")
}

9.枚舉中的函數

enum Wearable {
    enum Weight: Int {
        case Light = 2
    }

    enum Armor: Int {
        case Light = 2
    }

    case Helmet(weight: Weight, armor: Armor)


    func attributes() -> (weight: Int, armor: Int) {
        switch self {
        case .Helmet(let w, let a):
            return (weight: w.rawValue * 2, armor: a.rawValue * 4)

        }
    }
}

let test = Wearable.Helmet(weight: .Light, armor: .Light).attributes()
print(test)

enum Device {
    case iPad, iPhone, AppleTV, AppleWatch
    func introduced() -> String {
        switch self {
        case .AppleTV: return "\(self) was introduced 2006"
        case .iPhone: return "\(self) was introduced 2007"
        case .iPad: return "\(self) was introduced 2010"
        case .AppleWatch: return "\(self) was introduced 2014"
        }
    }
}
print (Device.iPhone.introduced())

10.枚舉中的屬性

enum Device1 {
    case iPad, iPhone
    var year: Int {
        switch self {
        case .iPad:
            return 2010
        case .iPhone:
            return 2007
    }
    }
}

let iPhone = Device1.iPhone
print(iPhone.year)

ParameterEncodingFailureReason

經過ParameterEncodingFailureReason咱們可以很清楚的看出來這是一個參數編碼的錯誤緣由。你們注意reason這個詞,在命名中,有或者沒有這個詞,表達的意境徹底不一樣,所以,Alamofire牛逼就體如今這些細節之中。json

public enum AFError: Error {
    /// The underlying reason the parameter encoding error occurred.
    ///
    /// - missingURL:                 The URL request did not have a URL to encode.
    /// - jsonEncodingFailed:         JSON serialization failed with an underlying system error during the
    ///                               encoding process.
    /// - propertyListEncodingFailed: Property list serialization failed with an underlying system error during
    ///                               encoding process.
    public enum ParameterEncodingFailureReason {
        case missingURL
        case jsonEncodingFailed(error: Error)
        case propertyListEncodingFailed(error: Error)
    }
 }

ParameterEncodingFailureReason自己是一個enum,同時,它又被包含在AFError之中,這說明枚舉之中能夠有另外一個枚舉。那麼像這種狀況咱們怎麼使用呢?看下邊的代碼:swift

let parameterErrorReason = AFError.ParameterEncodingFailureReason.missingURL

枚舉的訪問是一級一級進行的。咱們再看這行代碼:case jsonEncodingFailed(error: Error)jsonEncodingFailed(error: Error)並非函數,就是枚舉的一個普通的子選項(error: Error)是它的一個關聯值,相對於任何一個子選項,咱們均可以關聯任何值,它的意義就在於,把這些值與子選項進行綁定,方便在須要的時候調用。咱們會在下邊講解如何獲取關聯值。服務器

參數編碼有一下幾種方式:網絡

  • 把參數編碼到URL中
  • 把參數編碼到httpBody中

Alamofire中是如何進行參數編碼的,這方面的內容會在後續的ParameterEncoding.swift這一篇文章中給出詳細的解釋。那麼編碼失敗的緣由可能爲:框架

  • missingURL 給定的urlRequest.url爲nil的狀況拋出錯誤
  • jsonEncodingFailed(error: Error) 當選擇把參數編碼成JSON格式的狀況下,參數JSON化拋出的錯誤
  • propertyListEncodingFailed(error: Error) 這個同上

綜上所述,ParameterEncodingFailureReason封裝了參數編碼的錯誤,可能出現的錯誤類型爲Error,說明這些所謂通常是調用系統Api產生的錯誤。ide

MultipartEncodingFailureReason

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)
    }

多部分編碼錯誤通常發生在上傳或下載請求中對數據的處理過程當中,這裏邊最重要的是對上傳數據的處理過程,會在後續的MultipartFormData.swift這一篇文章中給出詳細的解釋,咱們就簡單的分析下MultipartEncodingFailureReason子選項錯誤出現的緣由:函數

  • bodyPartURLInvalid(url: URL) 上傳數據時,能夠經過fileURL的方式,讀取本地文件數據,若是fileURL不可用,就會拋出這個錯誤
  • bodyPartFilenameInvalid(in: URL) 若是使用fileURL的lastPathComponent或者pathExtension獲取filename爲空拋出的錯誤
  • bodyPartFileNotReachable(at: URL) 經過fileURL不能訪問數據,也就是不可達的
  • bodyPartFileNotReachableWithError(atURL: URL, error: Error) 這個不一樣於bodyPartFileNotReachable(at: URL),當嘗試檢測fileURL是否是可達的狀況下拋出的錯誤
  • bodyPartFileIsDirectory(at: URL) 當fileURL是一個文件夾時拋出錯誤
  • bodyPartFileSizeNotAvailable(at: URL) 當使用系統Api獲取fileURL指定文件的size出現錯誤
  • bodyPartFileSizeQueryFailedWithError(forURL: URL, error: Error) 查詢fileURL指定文件size出現錯誤
  • bodyPartInputStreamCreationFailed(for: URL) 經過fileURL建立inputStream出現錯誤
  • outputStreamCreationFailed(for: URL) 當嘗試把編碼後的數據寫入到硬盤時,建立outputStream出現錯誤
  • outputStreamFileAlreadyExists(at: URL) 數據不能被寫入,由於指定的fileURL已經存在
  • outputStreamURLInvalid(url: URL) fileURL不是一個file URL
  • outputStreamWriteFailed(error: Error) 數據流寫入錯誤
  • inputStreamReadFailed(error: Error) 數據流讀入錯誤

綜上所述,這些錯誤基本上都跟數據的操做相關,這個在後續會作出很詳細的說明。fetch

ResponseValidationFailureReason

public enum ResponseValidationFailureReason {
        case dataFileNil
        case dataFileReadFailed(at: URL)
        case missingContentType(acceptableContentTypes: [String])
        case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String)
        case unacceptableStatusCode(code: Int)
    }

Alamofire無論請求是否成功,都會返回response。它提供了驗證ContentType和StatusCode的功能,關於驗證,再後續的文章中會有詳細的解答,咱們先看看這些緣由:編碼

  • dataFileNil 保存數據的URL不存在,這種狀況通常出如今下載任務中,指的是下載代理中的fileURL缺失
  • dataFileReadFailed(at: URL) 保存數據的URL沒法讀取數據,同上
  • missingContentType(acceptableContentTypes: [String]) 服務器返回的response不包含ContentType且提供的acceptableContentTypes不包含通配符(通配符表示能夠接受任何類型)
  • unacceptableContentType(acceptableContentTypes: [String], responseContentType: String) ContentTypes不匹配
  • unacceptableStatusCode(code: Int) StatusCode不匹配

ResponseSerializationFailureReason

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)
}

咱們在Alamofire源碼解讀系列(一)之概述和使用中已經提到,Alamofire支持把服務器的response序列成幾種數據格式。url

  • response 直接返回HTTPResponse,未序列化
  • responseData 序列化爲Data
  • responseJSON 序列化爲Json
  • responseString 序列化爲字符串
  • responsePropertyList 序列化爲Any

那麼在序列化的過程當中,極可能會發生下邊的錯誤:

  • inputDataNil 服務器返回的response沒有數據
  • inputDataNilOrZeroLength 服務器返回的response沒有數據或者數據的長度是0
  • inputFileNil 指向數據的URL不存在
  • inputFileReadFailed(at: URL) 指向數據的URL沒法讀取數據
  • stringSerializationFailed(encoding: String.Encoding) 當使用指定的String.Encoding序列化數據爲字符串時,拋出的錯誤
  • jsonSerializationFailed(error: Error) JSON序列化錯誤
  • propertyListSerializationFailed(error: Error) plist序列化錯誤

AFError

上邊內容中介紹的ParameterEncodingFailureReason MultipartEncodingFailureReason ResponseValidationFailureReasonResponseSerializationFailureReason,他們是定義在AFError中獨立的枚舉,他們之間是包含和被包含的關係,理解這一點很重要,由於有了這種包含的管理,在使用中就須要經過AFError.ParameterEncodingFailureReason這種方式進行操做。

那麼最重要的問題就是,如何把上邊4個獨立的枚舉進行串聯呢?Alamofire巧妙的地方就在這裏,有4個獨立的枚舉,分別表明4大錯誤。也就是說這個網絡框架確定有這4大錯誤模塊,咱們只須要給AFError設計4個子選項,每一個子選項關聯上上邊4個獨立枚舉的值就ok了。

這個設計真的很巧妙,試想,若是把全部的錯誤都放到AFError中,就顯得很是冗餘。那麼下邊的代碼就呼之欲出了,你們好好體會體會在swift下這麼設計的妙用:

case invalidURL(url: URLConvertible)
case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
case responseValidationFailed(reason: ResponseValidationFailureReason)
case responseSerializationFailed(reason: ResponseSerializationFailureReason)

AFError的擴展

也許在開發中,咱們完成了上邊的代碼就認爲夠用了,但對於一個開源框架而言,遠遠是不夠的。咱們一點點進行剖析:

如今給定一條數據:

func findErrorType(error: AFError) {
        
    }

我只須要知道這個error是否是參數編碼錯誤,應該怎麼辦?所以爲AFError提供5個布爾類型的屬性,專門用來獲取當前的錯誤是否是某個指定的類型。這個功能的實現比較簡單,代碼以下:

extension AFError {
    /// Returns whether the AFError is an invalid URL error.
    public var isInvalidURLError: Bool {
        if case .invalidURL = self { return true }
        return false
    }

    /// Returns whether the AFError is a parameter encoding error. When `true`, the `underlyingError` property will
    /// contain the associated value.
    public var isParameterEncodingError: Bool {
        if case .parameterEncodingFailed = self { return true }
        return false
    }

    /// Returns whether the AFError is a multipart encoding error. When `true`, the `url` and `underlyingError` properties
    /// will contain the associated values.
    public var isMultipartEncodingError: Bool {
        if case .multipartEncodingFailed = self { return true }
        return false
    }

    /// Returns whether the `AFError` is a response validation error. When `true`, the `acceptableContentTypes`,
    /// `responseContentType`, and `responseCode` properties will contain the associated values.
    public var isResponseValidationError: Bool {
        if case .responseValidationFailed = self { return true }
        return false
    }

    /// Returns whether the `AFError` is a response serialization error. When `true`, the `failedStringEncoding` and
    /// `underlyingError` properties will contain the associated values.
    public var isResponseSerializationError: Bool {
        if case .responseSerializationFailed = self { return true }
        return false
    }
}

總而言之,這些都是給AFError這個枚舉擴展的屬性,還包含下邊這些屬性:

  • urlConvertible: URLConvertible? 獲取某個屬性,這個屬性實現了URLConvertible協議,在AFError中只有case invalidURL(url: URLConvertible)這個選項符合要求

    /// The `URLConvertible` associated with the error.
          public var urlConvertible: URLConvertible? {
              switch self {
              case .invalidURL(let url):
                  return url
              default:
                  return nil
              }
          }
  • url: URL? 獲取AFError中的URL,固然這個URL只跟MultipartEncodingFailureReason這個子選項有關

    /// The `URL` associated with the error.
          public var url: URL? {
              switch self {
              case .multipartEncodingFailed(let reason):
                  return reason.url
              default:
                  return nil
              }
          }
  • underlyingError: Error? AFError中封裝的全部的可能出現的錯誤中,並非每種可能都會返回Error這個錯誤信息,所以這個屬性是可選的

    /// The `Error` returned by a system framework associated with a `.parameterEncodingFailed`,
          /// `.multipartEncodingFailed` or `.responseSerializationFailed` error.
          public var underlyingError: Error? {
              switch self {
              case .parameterEncodingFailed(let reason):
                  return reason.underlyingError
              case .multipartEncodingFailed(let reason):
                  return reason.underlyingError
              case .responseSerializationFailed(let reason):
                  return reason.underlyingError
              default:
                  return nil
              }
          }
  • acceptableContentTypes: [String]? 可接受的ContentType

    /// The response `Content-Type` of a `.responseValidationFailed` error.
          public var responseContentType: String? {
              switch self {
              case .responseValidationFailed(let reason):
                  return reason.responseContentType
              default:
                  return nil
              }
          }
  • responseCode: Int? 響應碼

    /// The response code of a `.responseValidationFailed` error.
      public var responseCode: Int? {
          switch self {
          case .responseValidationFailed(let reason):
              return reason.responseCode
          default:
              return nil
          }
      }
  • failedStringEncoding: String.Encoding? 錯誤的字符串編碼

    /// The `String.Encoding` associated with a failed `.stringResponse()` call.
          public var failedStringEncoding: String.Encoding? {
              switch self {
              case .responseSerializationFailed(let reason):
                  return reason.failedStringEncoding
              default:
                  return nil
              }
          }

這裏是一個小的分割線,在上邊屬性的獲取中,也是用到了下邊代碼中的擴展功能:

extension AFError.ParameterEncodingFailureReason {
        var underlyingError: Error? {
            switch self {
            case .jsonEncodingFailed(let error), .propertyListEncodingFailed(let error):
                return error
            default:
                return nil
            }
        }
    }
    
    extension AFError.MultipartEncodingFailureReason {
        var url: URL? {
            switch self {
            case .bodyPartURLInvalid(let url), .bodyPartFilenameInvalid(let url), .bodyPartFileNotReachable(let url),
                 .bodyPartFileIsDirectory(let url), .bodyPartFileSizeNotAvailable(let url),
                 .bodyPartInputStreamCreationFailed(let url), .outputStreamCreationFailed(let url),
                 .outputStreamFileAlreadyExists(let url), .outputStreamURLInvalid(let url),
                 .bodyPartFileNotReachableWithError(let url, _), .bodyPartFileSizeQueryFailedWithError(let url, _):
                return url
            default:
                return nil
            }
        }
    
        var underlyingError: Error? {
            switch self {
            case .bodyPartFileNotReachableWithError(_, let error), .bodyPartFileSizeQueryFailedWithError(_, let error),
                 .outputStreamWriteFailed(let error), .inputStreamReadFailed(let error):
                return error
            default:
                return nil
            }
        }
    }
    
    extension AFError.ResponseValidationFailureReason {
        var acceptableContentTypes: [String]? {
            switch self {
            case .missingContentType(let types), .unacceptableContentType(let types, _):
                return types
            default:
                return nil
            }
        }
    
        var responseContentType: String? {
            switch self {
            case .unacceptableContentType(_, let responseType):
                return responseType
            default:
                return nil
            }
        }
    
        var responseCode: Int? {
            switch self {
            case .unacceptableStatusCode(let code):
                return code
            default:
                return nil
            }
        }
    }
    
    extension AFError.ResponseSerializationFailureReason {
        var failedStringEncoding: String.Encoding? {
            switch self {
            case .stringSerializationFailed(let encoding):
                return encoding
            default:
                return nil
            }
        }
    
        var underlyingError: Error? {
            switch self {
            case .jsonSerializationFailed(let error), .propertyListSerializationFailed(let error):
                return error
            default:
                return nil
            }
        }
    }

錯誤描述

在開發中,若是程序遇到錯誤,咱們每每會給用戶展現更加直觀的信息,這就要求咱們把錯誤信息轉換成易於理解的內容。所以咱們只要實現LocalizedError協議就行了。這裏邊的內容很簡單,在這裏就直接把代碼寫上了,不作分析:

extension AFError: LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .invalidURL(let url):
            return "URL is not valid: \(url)"
        case .parameterEncodingFailed(let reason):
            return reason.localizedDescription
        case .multipartEncodingFailed(let reason):
            return reason.localizedDescription
        case .responseValidationFailed(let reason):
            return reason.localizedDescription
        case .responseSerializationFailed(let reason):
            return reason.localizedDescription
        }
    }
}

extension AFError.ParameterEncodingFailureReason {
    var localizedDescription: String {
        switch self {
        case .missingURL:
            return "URL request to encode was missing a URL"
        case .jsonEncodingFailed(let error):
            return "JSON could not be encoded because of error:\n\(error.localizedDescription)"
        case .propertyListEncodingFailed(let error):
            return "PropertyList could not be encoded because of error:\n\(error.localizedDescription)"
        }
    }
}

extension AFError.MultipartEncodingFailureReason {
    var localizedDescription: String {
        switch self {
        case .bodyPartURLInvalid(let url):
            return "The URL provided is not a file URL: \(url)"
        case .bodyPartFilenameInvalid(let url):
            return "The URL provided does not have a valid filename: \(url)"
        case .bodyPartFileNotReachable(let url):
            return "The URL provided is not reachable: \(url)"
        case .bodyPartFileNotReachableWithError(let url, let error):
            return (
                "The system returned an error while checking the provided URL for " +
                "reachability.\nURL: \(url)\nError: \(error)"
            )
        case .bodyPartFileIsDirectory(let url):
            return "The URL provided is a directory: \(url)"
        case .bodyPartFileSizeNotAvailable(let url):
            return "Could not fetch the file size from the provided URL: \(url)"
        case .bodyPartFileSizeQueryFailedWithError(let url, let error):
            return (
                "The system returned an error while attempting to fetch the file size from the " +
                "provided URL.\nURL: \(url)\nError: \(error)"
            )
        case .bodyPartInputStreamCreationFailed(let url):
            return "Failed to create an InputStream for the provided URL: \(url)"
        case .outputStreamCreationFailed(let url):
            return "Failed to create an OutputStream for URL: \(url)"
        case .outputStreamFileAlreadyExists(let url):
            return "A file already exists at the provided URL: \(url)"
        case .outputStreamURLInvalid(let url):
            return "The provided OutputStream URL is invalid: \(url)"
        case .outputStreamWriteFailed(let error):
            return "OutputStream write failed with error: \(error)"
        case .inputStreamReadFailed(let error):
            return "InputStream read failed with error: \(error)"
        }
    }
}

extension AFError.ResponseSerializationFailureReason {
    var localizedDescription: String {
        switch self {
        case .inputDataNil:
            return "Response could not be serialized, input data was nil."
        case .inputDataNilOrZeroLength:
            return "Response could not be serialized, input data was nil or zero length."
        case .inputFileNil:
            return "Response could not be serialized, input file was nil."
        case .inputFileReadFailed(let url):
            return "Response could not be serialized, input file could not be read: \(url)."
        case .stringSerializationFailed(let encoding):
            return "String could not be serialized with encoding: \(encoding)."
        case .jsonSerializationFailed(let error):
            return "JSON could not be serialized because of error:\n\(error.localizedDescription)"
        case .propertyListSerializationFailed(let error):
            return "PropertyList could not be serialized because of error:\n\(error.localizedDescription)"
        }
    }
}

extension AFError.ResponseValidationFailureReason {
    var localizedDescription: String {
        switch self {
        case .dataFileNil:
            return "Response could not be validated, data file was nil."
        case .dataFileReadFailed(let url):
            return "Response could not be validated, data file could not be read: \(url)."
        case .missingContentType(let types):
            return (
                "Response Content-Type was missing and acceptable content types " +
                "(\(types.joined(separator: ","))) do not match \"*/*\"."
            )
        case .unacceptableContentType(let acceptableTypes, let responseType):
            return (
                "Response Content-Type \"\(responseType)\" does not match any acceptable types: " +
                "\(acceptableTypes.joined(separator: ","))."
            )
        case .unacceptableStatusCode(let code):
            return "Response status code was unacceptable: \(code)."
        }
    }
}

做者:老馬的春天 連接:https://www.jianshu.com/p/99e6ba32f244 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索