Alamofire源碼解讀系列(一)之概述和使用

盡管Alamofire的github文檔已經(jīng)做了很詳細的說明锰霜,我還是想重新梳理一遍它的各種用法威鹿,以及這些方法的一些設(shè)計思想

前言

因為之前寫過一個AFNetworking的源碼解讀戴差,所以就已經(jīng)比較了解iOS平臺的網(wǎng)絡(luò)框架是怎么一回事了。AlamofireAFNetworking有很多相同的地方冠胯,然而阀蒂,這些相同點在swift和oc兩種不同語言的實現(xiàn)情況下,給人的感覺是完全不同的酌心。

我們看源碼的目的有兩個:一是了解代碼的實現(xiàn)原理拌消,另一個是學(xué)習(xí)swift的一些高級用法。

下邊的這個表格就是我打算解讀的順序安券,一共17個文件墩崩,其中DispatchQueue+Alamofire.swift就不作為單獨的一篇來解釋了,會在使用到它的地方做一個說明侯勉,這一篇文章的主要目的就是解釋Alamofire如何使用鹦筹,因此一共就需要17篇文章來完成這一系列的源碼解讀。

文件名 描述
1.AFError.swift 對錯誤的封裝址貌,包含了Alamofire中所有可能出現(xiàn)的錯誤铐拐,使用enum實現(xiàn),很有意思
2.Notifications.swift swift中通知的用法练对,這個跟oc的有區(qū)別
3.ParameterEncoding.swift 參數(shù)編碼遍蟋,有些情況需要把參數(shù)編碼到URL中,包含了轉(zhuǎn)義相關(guān)的知識
4.Result.swift 對請求結(jié)果的封裝
5.TaskDelegate.swift 任務(wù)代理
6.NetworkReachabilityManager.swift 網(wǎng)絡(luò)狀態(tài)管理
7.ServerTrustPolicy.swift 安全策略管理
8.Response.swift 服務(wù)器返回的數(shù)據(jù)的封裝
9.ResponseSerialization.swift 響應(yīng)序列化管理
10.MultipartFormData.swift 多表單數(shù)據(jù)處理
11.Timeline.swift 新增的內(nèi)容螟凭,與請求相關(guān)的一些時間屬性
12.Request.swift 最核心的請求類
13.Validation.swift 對服務(wù)器響應(yīng)的驗證
14.SessionDelegate.swift 會話代理
15.SessionManager.swift 會話管理虚青,核心內(nèi)容
16.Alamofire.swift 支持的基本接口

Alamofire的基本用法

1.最簡單的請求

Alamofire.request("https://httpbin.org/get")

這是一個最簡單的請求,這個請求即不需要參數(shù)赂摆,也不需要接收數(shù)據(jù)挟憔。接下來我們翻看Alamofire這個文件,發(fā)現(xiàn)并沒有Alamofire這個類烟号,那么為什么能夠像Alamofire.requeset()這么使用呢绊谭?

其實當(dāng)一個文件作為一個模塊被導(dǎo)入的話,通過文件名就能訪問到模塊內(nèi)部的數(shù)據(jù)汪拥,比如說通過cocopods導(dǎo)入的框架达传,就有這樣的特性。如果把Alamofire.swift直接拖進工程中,Alamofire.requeset()就會報錯宪赶,但是我們?nèi)サ鬉lamofire宗弯,直接用request()就可以了。

2.Response處理

Alamofire.request("https://httpbin.org/get").responseJSON { response in
    print(response.request)  // original URL request
    print(response.response) // HTTP URL response
    print(response.data)     // server data
    print(response.result)   // result of response serialization

    if let JSON = response.result.value {
        print("JSON: \(JSON)")
    }
}

在Alamofire中搂妻,對請求的封裝有以下幾種類型:

  • Request
  • DataRequest
  • DownloadRequest
  • UploadRequest
  • StreamRequest

這幾種類型蒙保,按照名字我們就能很容易的知道他們的用途是什么,其中StreamRequest在iOS9.0之后才被引入欲主。

request(...)方法返回Request本身或者其子類邓厕,那么responseJson就應(yīng)該是Request本身或者其子類的一個函數(shù),該函數(shù)的最后一個參數(shù)是一個閉包扁瓢。這里先不能解釋太多详恼,到了后邊會詳細解釋。

Alamofire對于response提供了5種處理方式:

// Response Handler - Unserialized Response
func response(
    queue: DispatchQueue?,
    completionHandler: @escaping (DefaultDataResponse) -> Void)
    -> Self

// Response Data Handler - Serialized into Data
func responseData(
    queue: DispatchQueue?,
    completionHandler: @escaping (DataResponse<Data>) -> Void)
    -> Self

// Response String Handler - Serialized into String
func responseString(
    queue: DispatchQueue?,
    encoding: String.Encoding?,
    completionHandler: @escaping (DataResponse<String>) -> Void)
    -> Self

// Response JSON Handler - Serialized into Any
func responseJSON(
    queue: DispatchQueue?,
    completionHandler: @escaping (DataResponse<Any>) -> Void)
    -> Self

// Response PropertyList (plist) Handler - Serialized into Any
func responsePropertyList(
    queue: DispatchQueue?,
    completionHandler: @escaping (DataResponse<Any>) -> Void))
    -> Self

我們把這五種歸納一下:

  • response 直接返回HTTPResponse引几,未序列化
  • responseData 序列化為Data
  • responseJSON 序列化為JSON
  • responseString 序列化為字符串
  • responsePropertyList 序列化為Any

不管被序列成哪一個昧互,結(jié)果都會通過閉包的參數(shù)response返回,如果是被序列化的數(shù)據(jù)伟桅,就通過resonse中的result.value來獲取數(shù)據(jù)敞掘。

源碼中response閉包函數(shù)的返回值是Self,也就是Request贿讹,這就讓我們能夠使用鏈?zhǔn)皆L問來做一些很有意思的事情渐逃,比如:

Alamofire.request("https://httpbin.org/get")
    .responseString { response in
        print("Response String: \(response.result.value)")
    }
    .responseJSON { response in
        print("Response JSON: \(response.result.value)")
    }

上邊的代碼就使用了鏈?zhǔn)皆L問,當(dāng)收到服務(wù)器的數(shù)據(jù)后民褂,先處理responseString再處理responseJSON。那么內(nèi)部是如何實現(xiàn)類似這種有順序的訪問的呢疯潭?

答案就是使用隊列赊堪,任務(wù)按照順序依次放入到隊列中,就實現(xiàn)了上邊的功能竖哩,這里關(guān)于隊列在Alamofire中是如何使用的哭廉,會在接下來的文章中給出更詳細的解答。我在這里先給出一個粗略的說明:

  1. TaskDelegate中有一個屬性queue,下邊就是這個queue的初始化,這樣的寫法也是通過閉包來實現(xiàn)賦值的相叁,值得注意的是operationQueue的isSuspended被賦值為true遵绰,這樣做的目的就是,當(dāng)一系列的operation被添加到隊列中后增淹,不會立刻執(zhí)行椿访,直到isSuspended等于false時才會。

       self.queue = {
                 let operationQueue = OperationQueue()
     
                 operationQueue.maxConcurrentOperationCount = 1
                 operationQueue.isSuspended = true
                 operationQueue.qualityOfService = .utility
     
                 return operationQueue
             }()
    
  2. 調(diào)用.responseString后放生了什么虑润?其實,很簡單,就是給queue添加了一個操作

     delegate.queue.addOperation {
                 /// 這里就調(diào)用了responseSerializer保存的系列化函數(shù)根灯,函數(shù)調(diào)用后會得到result
                 let result = responseSerializer.serializeResponse(
                     self.request,
                     self.response,
                     self.delegate.data,
                     self.delegate.error
                 )
     
                 /// 這里一定要記得,DataResponse是一個結(jié)構(gòu)體猪腕,是專門為了純存儲數(shù)據(jù)的,這里是調(diào)用了結(jié)構(gòu)體的初始化方法創(chuàng)建了一個新的DataResponse實例
                 var dataResponse = DataResponse<T.SerializedObject>(
                     request: self.request,
                     response: self.response,
                     data: self.delegate.data,
                     result: result,
                     timeline: self.timeline
                 )
     
                 dataResponse.add(self.delegate.metrics)
     
                 (queue ?? DispatchQueue.main).async { completionHandler(dataResponse) }
             }
    
  3. 當(dāng)然還有其他的一些操作钦勘,比方說上傳完成后要刪除臨時文件等等陋葡,但歸根到底,這里用的就是隊列相關(guān)的知識

Alamofire中彻采,默認(rèn)的響應(yīng)會放在主線程腐缤,那么我們該如何自定義響應(yīng)線程呢?

let utilityQueue = DispatchQueue.global(qos: .utility)

Alamofire.request("https://httpbin.org/get").responseJSON(queue: utilityQueue) { response in
    print("Executing response handler on utility queue")
}

這主要得益于swift函數(shù)的參數(shù)可以設(shè)置默認(rèn)值颊亮,有默認(rèn)值得函數(shù)參數(shù)可以忽略柴梆。

3.驗證

Alamofire.request("https://httpbin.org/get")
    .validate(statusCode: 200..<300)
    .validate(contentType: ["application/json"])
    .responseData { response in
        switch response.result {
        case .success:
            print("Validation Successful")
        case .failure(let error):
            print(error)
        }
    }

上邊的這些代碼看上去很簡單,其實包含了一個復(fù)雜的過程终惑。validate(statusCode: 200..<300)validate(contentType: ["application/json"])都返回的是Self绍在,只有這樣才能夠保證鏈?zhǔn)降恼{(diào)用。那么這兩個驗證的結(jié)果要如何來獲取呢雹有?

我們先看一個方法:

  @discardableResult
    public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
        return validate { [unowned self] _, response, _ in
            return self.validate(statusCode: acceptableStatusCodes, response: response)
        }
    }

這個方法就是validate(statusCode: 200..<300)的內(nèi)部實現(xiàn)函數(shù)偿渡,可以看出來,在函數(shù)中調(diào)用了一個函數(shù)得到的返回值霸奕,那么這個被調(diào)用的函數(shù)validate只接受一個參數(shù)溜宽,這個參數(shù)也是一個函數(shù)。我們姑且稱這個函數(shù)為函數(shù)1. 接下來要看看validate函數(shù)的實現(xiàn)細節(jié):

 @discardableResult
    public func validate(_ validation: @escaping Validation) -> Self {
        let validationExecution: () -> Void = { [unowned self] in
            if
                let response = self.response,
                self.delegate.error == nil,
                case let .failure(error) = validation(self.request, response, self.delegate.data)
            {
                self.delegate.error = error
            }
        }

        validations.append(validationExecution)

        return self
    }

可以看出质帅,函數(shù)內(nèi)部調(diào)用了它的參數(shù)适揉,這個參數(shù)也就是在上邊傳遞過來的函數(shù)1。這個可能比較繞煤惩,不太好理解嫉嘀。這個會在ResponseSerialization.swift那篇文章中進行詳細解釋的。

雖然我們可能通過下邊的方法來判斷是不是驗證成功:

switch response.result {
        case .success:
            print("Validation Successful")
        case .failure(let error):
            print(error)
        }

我們?nèi)匀豢梢酝ㄟ^result訪問到序列化后的數(shù)據(jù)

switch response.result {
        case .success(data):
            print("Validation Successful data:\(data)")
        case .failure(let error):
            print(error)
        }

如果使用自動驗證的話魄揉,它會驗證200..<300的狀態(tài)嗎和發(fā)請求時提供的可接受的ContentType類型剪侮。

Alamofire.request("https://httpbin.org/get").validate().responseJSON { response in
    switch response.result {
    case .success:
        print("Validation Successful")
    case .failure(let error):
        print(error)
    }
}

4.HTTP方法

public enum HTTPMethod: String {
    case options = "OPTIONS"
    case get     = "GET"
    case head    = "HEAD"
    case post    = "POST"
    case put     = "PUT"
    case patch   = "PATCH"
    case delete  = "DELETE"
    case trace   = "TRACE"
    case connect = "CONNECT"
}

Alamofire提供了上邊的HTTPMethod,至于每個方法的使用詳情洛退,請參考我寫的這篇文章瓣俯。那么在請求中是這么使用的:

Alamofire.request("https://httpbin.org/get") // method defaults to `.get`

Alamofire.request("https://httpbin.org/post", method: .post)
Alamofire.request("https://httpbin.org/put", method: .put)
Alamofire.request("https://httpbin.org/delete", method: .delete)

5.Parameter Encoding

Alamofire支持三種參數(shù)編碼方式:URLJSONPropertyList兵怯。也可以通過實現(xiàn)ParameterEncoding協(xié)議來自定義編碼方式彩匕。

我們先看URL編碼:

URLEncoding是對URL編碼的封裝,通過一個enum提供3種編碼方式:

 public enum Destination {
        case methodDependent, queryString, httpBody
    }
  • methodDependent 表示根據(jù)HTTPMethod來判斷如何編碼摇零,.get, .head, .delete情況下會把參數(shù)編入URL之中
  • queryString 表示把參數(shù)編入URL之中
  • httpBody 表示把參數(shù)編入httpBody之中

當(dāng)然這些東西現(xiàn)不在這里做過多的解釋了推掸,在開發(fā)中也用的不多桶蝎,詳細的解釋會放到后邊ParameterEncoding.swift這一片文章之中。

JSON

我們把參數(shù)以JSON的方式編碼谅畅,如果在開發(fā)中用到了登渣,需要在請求的header中設(shè)置

ContentTypeapplication/json

let parameters: Parameters = [
    "foo": [1,2,3],
    "bar": [
        "baz": "qux"
    ]
]

// Both calls are equivalent
Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: JSONEncoding.default)
Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: JSONEncoding(options: []))

// HTTP body: {"foo": [1, 2, 3], "bar": {"baz": "qux"}}

PropertyList

這個跟JSON很像毡泻,如果在開發(fā)中用到了胜茧,需要在請求的header中設(shè)置

ContentTypeapplication/x-plist

如果我們要自定義參數(shù)編碼仇味,那該怎么辦呢呻顽?下邊是Alamofire的一個例子:

struct JSONStringArrayEncoding: ParameterEncoding {
    private let array: [String]

    init(array: [String]) {
        self.array = array
    }

    func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
        var urlRequest = urlRequest.urlRequest

        let data = try JSONSerialization.data(withJSONObject: array, options: [])

        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
            urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
        }

        urlRequest.httpBody = data

        return urlRequest
    }
}

該例子中的JSONStringArrayEncoding實現(xiàn)了ParameterEncoding協(xié)議,實現(xiàn)了協(xié)議中的方法丹墨,這是一個典型的自定義編碼方式廊遍,在開發(fā)中這么使用:

Alamofire.request("https://xxxxx", method: .get, parameters: nil, encoding: JSONStringArrayEncoding(array: ["abc", "ddd"]), headers: nil)

當(dāng)然我們也可以把ParameterEncoding當(dāng)做一個API來使用:

let url = URL(string: "https://httpbin.org/get")!
var urlRequest = URLRequest(url: url)

let parameters: Parameters = ["foo": "bar"]
let encodedURLRequest = try URLEncoding.queryString.encode(urlRequest, with: parameters)

6.請求頭

客戶端每發(fā)起一次HTTP請求,請求頭信息是必不可少的贩挣。這也是同服務(wù)器交流的一種手段喉前,在實際的開發(fā)中,也肯定會遇到需要自定義請求頭的需求王财,那么我們就看看卵迂,在Alamofire中如何設(shè)置請求頭:

let headers: HTTPHeaders = [
    "Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
    "Accept": "application/json"
]

Alamofire.request("https://httpbin.org/headers", headers: headers).responseJSON { response in
    debugPrint(response)
}

很簡單,在request(...)函數(shù)中绒净,存在headers這么一個參數(shù)见咒,我們只要傳入提前寫好的字典就行了。當(dāng)然挂疆,使用URLSessionConfiguration來配置全局的屬性更加有優(yōu)勢改览,因為上邊的方法只是針對某一個請求的,如果有很多的請求都需要添加請求頭缤言,那么就應(yīng)該使用URLSessionConfiguration來配置了恃疯。

需要說明的是,Alamofire為每一個請求都設(shè)置了默認(rèn)的請求頭墨闲,我們簡單介紹一下:

  • Accept-Encoding 表示可接受的編碼方式,值為:gzip;q=1.0, compress;q=0.5
  • Accept-Language 表示可接受的語言郑口,這個在后邊的文章中會詳細說明
  • User-Agent 表示用戶代理信息鸳碧,比如:iOS Example/1.0 (com.alamofire.iOS-Example; build:1; iOS 10.0.0) Alamofire/4.0.0

默認(rèn)的情況下,我們通過SessionManager.default來創(chuàng)建SessionManager:

   open static let `default`: SessionManager = {
        let configuration = URLSessionConfiguration.default
        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders

        return SessionManager(configuration: configuration)
    }()

如果我們想自定義Accept-Encoding Accept-Language User-Agent犬性,那該怎么辦呢瞻离? 答案就是使用下邊的這個方法:

  public init(
        configuration: URLSessionConfiguration = URLSessionConfiguration.default,
        delegate: SessionDelegate = SessionDelegate(),
        serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
    {
        self.delegate = delegate
        self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)

        commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
    }

通過configuration來設(shè)置自定義的請求頭,但需要注意的是乒裆,通過這個初始化方法創(chuàng)建的SessionManager不在是一個單利了套利,要想繼續(xù)使用單利,可能需要自己繼承SessionManager,然后手動實現(xiàn)單利肉迫。

7.HTTP 基本認(rèn)證

在Alamofire中有三種使用基本認(rèn)證的方法:

  • 在request(...)和response之間验辞,拼接authenticate(user: user, password: password)

      let user = "user"
      let password = "password"
      
      Alamofire.request("https://httpbin.org/basic-auth/\(user)/\(password)")
          .authenticate(user: user, password: password)
          .responseJSON { response in
              debugPrint(response)
          }
    
  • 手動生成headers,Request.authorizationHeader(user: user, password: password)返回一個元組(key: String, value: String)?

      let user = "user"
      let password = "password"
      
      var headers: HTTPHeaders = [:]
      
      if let authorizationHeader = Request.authorizationHeader(user: user, password: password) {
          headers[authorizationHeader.key] = authorizationHeader.value
      }
      
      Alamofire.request("https://httpbin.org/basic-auth/user/password", headers: headers)
          .responseJSON { response in
              debugPrint(response)
          }
    
  • 使用URLCredential

      let user = "user"
      let password = "password"
      
      let credential = URLCredential(user: user, password: password, persistence: .forSession)
      
      Alamofire.request("https://httpbin.org/basic-auth/\(user)/\(password)")
          .authenticate(usingCredential: credential)
          .responseJSON { response in
              debugPrint(response)
          }
    

8.下載文件

Alamofire允許把服務(wù)器返回的數(shù)據(jù)加載到內(nèi)存或硬盤之中喊衫,**凡是以Alamofire.request開頭的請求都是把數(shù)據(jù)加載進內(nèi)存跌造,那么為什么還要區(qū)分內(nèi)存和硬盤呢?相對于比較小的數(shù)據(jù)族购,加載進內(nèi)存是高效的壳贪,但對于比較大的文件,加載進內(nèi)存確實災(zāi)難性的寝杖,因為很可能造成內(nèi)存崩潰违施。因此,在處理大文件這個問題上瑟幕,我們應(yīng)該用Alamofire.download把數(shù)據(jù)保存到一個臨時的本地文件中磕蒲。

比如,我們獲取一個圖片:

Alamofire.download("https://httpbin.org/image/png").responseData { response in
    if let data = response.result.value {
        let image = UIImage(data: data)
    }
}

即使APP在后臺收苏,download也是支持的亿卤。

需要注意的是,Alamofire.download返回的是DownloadRequest鹿霸,它的response的類型是DownloadResponse排吴,這里邊包含temporaryURLdestinationURL這兩個屬性,也就是說懦鼠,如果我們沒有指定Destination钻哩,那么文件就默認(rèn)下載到temporaryURL,通過他也可以訪問到文件肛冶。

要想自定義指定的目標(biāo)路徑街氢,我們需要創(chuàng)建一個DownloadFileDestination的閉包,我們先看看這個閉包的原型:

public typealias DownloadFileDestination = (
    _ temporaryURL: URL,
    _ response: HTTPURLResponse)
    -> (destinationURL: URL, options: DownloadOptions)

可以看出睦袖,該函數(shù)有兩個參數(shù)珊肃,temporaryURL和response,要求返回一個元組馅笙,包含目標(biāo)路徑和選型伦乔,我們在看看這個DownloadOptions:

  • createIntermediateDirectories 表示會根據(jù)路徑來創(chuàng)建中間的文件夾
  • removePreviousFile 表示會移除指定路徑上之前的文件

這里指的注意的是DownloadOptions使用掩碼來實現(xiàn)的,這就說明可以同時選中這兩個選項 我們來看個例子:

let destination: DownloadRequest.DownloadFileDestination = { _, _ in
    let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    let fileURL = documentsURL.appendPathComponent("pig.png")

    return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}

Alamofire.download(urlString, to: destination).response { response in
    print(response)

    if response.error == nil, let imagePath = response.destinationURL?.path {
        let image = UIImage(contentsOfFile: imagePath)
    }
}

另外一種用法就是使用Alamofire建議的路徑董习,我們先看一個例子:

let destination = DownloadRequest.suggestedDownloadDestination(directory: .documentDirectory)
Alamofire.download("https://httpbin.org/image/png", to: destination)

再來看看suggestedDownloadDestination函數(shù)的實現(xiàn):

 open class func suggestedDownloadDestination(
        for directory: FileManager.SearchPathDirectory = .documentDirectory,
        in domain: FileManager.SearchPathDomainMask = .userDomainMask)
        -> DownloadFileDestination
    {
        return { temporaryURL, response in
            let directoryURLs = FileManager.default.urls(for: directory, in: domain)

            if !directoryURLs.isEmpty {
                return (directoryURLs[0].appendingPathComponent(response.suggestedFilename!), [])
            }

            return (temporaryURL, [])
        }
    }

可以看出來烈和,suggestedDownloadDestination需要指定directory和domain,當(dāng)然他們也都有默認(rèn)值皿淋,文件名則采用的是response.suggestedFilename!

說道下載招刹,就不得不提下載進度恬试,我們來看看Alamofire是怎么用下載進度的:

Alamofire.download("https://httpbin.org/image/png")
    .downloadProgress { progress in
        print("Download Progress: \(progress.fractionCompleted)")
    }
    .responseData { response in
        if let data = response.result.value {
            let image = UIImage(data: data)
        }
    }

大概說一下監(jiān)聽進度的基本原理,詳細的實現(xiàn)方法會在后續(xù)的文章中提供疯暑,當(dāng)下載文件開始之后训柴,就會有一個數(shù)據(jù)寫入的代理方法被調(diào)用,就是在這個方法中處理進度的缰儿。我們看看這個進度函數(shù):

@discardableResult
open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
    dataDelegate.progressHandler = (closure, queue)
    return self
}

可以看出來除了一個閉包參數(shù)意外還有另外一個參數(shù)畦粮,就是隊列,作用就是指定閉包在那個隊列中被調(diào)用乖阵,我們在開發(fā)中宣赔,這么使用:

let utilityQueue = DispatchQueue.global(qos: .utility)

Alamofire.download("https://httpbin.org/image/png")
    .downloadProgress(queue: utilityQueue) { progress in
        print("Download Progress: \(progress.fractionCompleted)")
    }
    .responseData { response in
        if let data = response.result.value {
            let image = UIImage(data: data)
        }
    }

還有一種特殊的情況,就是恢復(fù)下載數(shù)據(jù)瞪浸,當(dāng)一個下載任務(wù)因為一些原因被取消或者中斷后儒将,后返回一個resumeData,我們可以使用這個resumeData重新發(fā)起一個請求对蒲,具體使用方法如下:

class ImageRequestor {
    private var resumeData: Data?
    private var image: UIImage?

    func fetchImage(completion: (UIImage?) -> Void) {
        guard image == nil else { completion(image) ; return }

        let destination: DownloadRequest.DownloadFileDestination = { _, _ in
            let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            let fileURL = documentsURL.appendPathComponent("pig.png")

            return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
        }

        let request: DownloadRequest

        if let resumeData = resumeData {
            request = Alamofire.download(resumingWith: resumeData)
        } else {
            request = Alamofire.download("https://httpbin.org/image/png")
        }

        request.responseData { response in
            switch response.result {
            case .success(let data):
                self.image = UIImage(data: data)
            case .failure:
                self.resumeData = response.resumeData
            }
        }
    }
}

9.上傳文件

在開發(fā)中钩蚊,當(dāng)需要上傳的數(shù)據(jù)很小的時候蹈矮,我們往往通過JSON或者URL把參數(shù)上傳到服務(wù)器砰逻,但是遇到數(shù)據(jù)量比較大的情況,在Alamofire中就要采用upload的方式上傳數(shù)據(jù)泛鸟。

假設(shè)我們有一張圖片要上傳:

let imageData = UIPNGRepresentation(image)!

Alamofire.upload(imageData, to: "https://httpbin.org/post").responseJSON { response in
    debugPrint(response)
}

或者這樣上傳:

let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")

Alamofire.upload(fileURL, to: "https://httpbin.org/post").responseJSON { response in
    debugPrint(response)
}

在Alamofire中處理上傳數(shù)據(jù)的方式有以下幾種:

  • Data
  • fileURL
  • inputStream
  • MultipartFormData

前三種用起來比較簡單蝠咆,我們接下來講講MultipartFormData的使用方法:

Alamofire.upload(
    multipartFormData: { multipartFormData in
        multipartFormData.append(unicornImageURL, withName: "unicorn")
        multipartFormData.append(rainbowImageURL, withName: "rainbow")
    },
    to: "https://httpbin.org/post",
    encodingCompletion: { encodingResult in
        switch encodingResult {
        case .success(let upload, _, _):
            upload.responseJSON { response in
                debugPrint(response)
            }
        case .failure(let encodingError):
            print(encodingError)
        }
    }
)

這段代碼需要注意的有幾個地方。

  • 數(shù)據(jù)是通過 multipartFormData.append拼接起來的北滥,append需要兩個參數(shù)刚操,其中一個參數(shù)是獲取數(shù)據(jù)的方式,另一個是數(shù)據(jù)名稱再芋,這個名稱一定要給菊霜,主要用于給多表單數(shù)據(jù)的Content-Disposition中的name字段賦值。這個在后續(xù)的文章中也會給出詳細解釋济赎。
  • encodingCompletion并不是上傳成功后的回調(diào)函數(shù)鉴逞,而是所有要上傳的數(shù)據(jù)編碼后的回調(diào)。那么我們需要對編碼結(jié)果做出判斷司训,這樣做的好處就是华蜒,如果數(shù)據(jù)編碼失敗了,就沒必要發(fā)送數(shù)據(jù)給服務(wù)器豁遭。
  • encodingResult的結(jié)果,如果是成功的贺拣,那么它會返回一個UploadRequest蓖谢,我們就通過這個UploadRequest綁定response事件捂蕴。

再就是在上傳文件的時候監(jiān)聽進度了,使用方法:

let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")

Alamofire.upload(fileURL, to: "https://httpbin.org/post")
    .uploadProgress { progress in // main queue by default
        print("Upload Progress: \(progress.fractionCompleted)")
    }
    .downloadProgress { progress in // main queue by default
        print("Download Progress: \(progress.fractionCompleted)")
    }
    .responseJSON { response in
        debugPrint(response)
    }

10.統(tǒng)計度量

Alamofire提供了一個叫TimeLine的新特性闪幽,通過這個特性啥辨,我們能夠觀察跟請求相關(guān)的一些時間屬性,使用方法如下:

Alamofire.request("https://httpbin.org/get").responseJSON { response in
    print(response.timeline)
}

打印結(jié)果如下:

Latency: 0.428 seconds
Request Duration: 0.428 seconds
Serialization Duration: 0.001 seconds
Total Duration: 0.429 seconds

在ios10中盯腌,蘋果引入了URLSessionTaskMetrics 溉知,這個APIs能夠提供很多跟請求響應(yīng)相關(guān)的信息,在Alamofire中通過response.metrics來訪問這個屬性:

Alamofire.request("https://httpbin.org/get").responseJSON { response in
    print(response.metrics)
}

在使用的時候腕够,一定要做版本檢測:

Alamofire.request("https://httpbin.org/get").responseJSON { response in
    if #available(iOS 10.0. *) {
        print(response.metrics)
    }
}   

11.打印請求

在開發(fā)中级乍,經(jīng)常做的一件事就是調(diào)試接口,如果有一種方案帚湘,能夠很容易的打印請求相關(guān)的參數(shù)玫荣,那么就再好不過了。Alamofire中的Request實現(xiàn)了CustomStringConvertibleCustomDebugStringConvertible協(xié)議大诸,因此我們就可以通過下邊的方法來打印請求信息:

let request = Alamofire.request("https://httpbin.org/ip")

print(request)
// GET https://httpbin.org/ip (200)

打印調(diào)試模式下的信息:

let request = Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"])
debugPrint(request)

結(jié)果如下:

$ curl -i \
    -H "User-Agent: Alamofire/4.0.0" \
    -H "Accept-Encoding: gzip;q=1.0, compress;q=0.5" \
    -H "Accept-Language: en;q=1.0,fr;q=0.9,de;q=0.8,zh-Hans;q=0.7,zh-Hant;q=0.6,ja;q=0.5" \
    "https://httpbin.org/get?foo=bar"

Alamofire的高級用法

1.Session Manager

Alamofire有一些高級的使用方法捅厂,最外層的方法都是通過Alamofire.request來訪問的,其內(nèi)部是通過Alamofire.SessionManagerURLSessionConfiguration來實現(xiàn)的资柔,因此我們可以通過修改這些屬性焙贷,來靈活的使用Request。

先看下邊的兩種請求方式贿堰,他們的作用是一樣的:

Alamofire.request("https://httpbin.org/get")

let sessionManager = Alamofire.SessionManager.default
sessionManager.request("https://httpbin.org/get")

通過URLSessionConfiguration我們能夠很靈活的修改網(wǎng)絡(luò)配置參數(shù)辙芍,比如超時時間等等,下邊我們就使用URLSessionConfiguration來創(chuàng)建SessionManager官边。

使用Default Configuration創(chuàng)建SessionManage

let configuration = URLSessionConfiguration.default
let sessionManager = Alamofire.SessionManager(configuration: configuration)

使用Background Configuration創(chuàng)建SessionManage

let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background")
let sessionManager = Alamofire.SessionManager(configuration: configuration)

使用Ephemeral Configuration創(chuàng)建SessionManage

let configuration = URLSessionConfiguration.ephemeral
let sessionManager = Alamofire.SessionManager(configuration: configuration)

修改Configuration

var defaultHeaders = Alamofire.SessionManager.defaultHTTPHeaders
defaultHeaders["DNT"] = "1 (Do Not Track Enabled)"

let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = defaultHeaders

let sessionManager = Alamofire.SessionManager(configuration: configuration)

對于AuthorizationContent-Type不建議通過Configuration來配置沸手,建議使用Alamofire.request APIs中的headers來配置。

2.Session Delegate

在開發(fā)中注簿,會有很多自定義代理事件的需求契吉,Alamofire中提供了很多的閉包來解決這個問題,比如:

/// Overrides default behavior for URLSessionDelegate method `urlSession(_:didReceive:completionHandler:)`.
open var sessionDidReceiveChallenge: ((URLSession, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?

/// Overrides default behavior for URLSessionDelegate method `urlSessionDidFinishEvents(forBackgroundURLSession:)`.
open var sessionDidFinishEventsForBackgroundURLSession: ((URLSession) -> Void)?

/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)`.
open var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?

/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:willCacheResponse:completionHandler:)`.
open var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?

我們有兩種方法來修改Alamofire中默認(rèn)的代理事件诡渴,一種是重寫這些代理函數(shù):

let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default)
let delegate: Alamofire.SessionDelegate = sessionManager.delegate

delegate.taskWillPerformHTTPRedirection = { session, task, response, request in
    var finalRequest = request

    if
        let originalRequest = task.originalRequest,
        let urlString = originalRequest.url?.urlString,
        urlString.contains("apple.com")
    {
        finalRequest = originalRequest
    }

    return finalRequest
}

上邊的函數(shù)中捐晶,我們重新定義了重定向的函數(shù)。還有一種方法是繼承代理后妄辩,重寫父類的方法:

class LoggingSessionDelegate: SessionDelegate {
    override func urlSession(
        _ session: URLSession,
        task: URLSessionTask,
        willPerformHTTPRedirection response: HTTPURLResponse,
        newRequest request: URLRequest,
        completionHandler: @escaping (URLRequest?) -> Void)
    {
        print("URLSession will perform HTTP redirection to request: \(request)")

        super.urlSession(
            session,
            task: task,
            willPerformHTTPRedirection: response,
            newRequest: request,
            completionHandler: completionHandler
        )
    }
}

3.Request

request,download, upload stream這四個方法的返回值分別為DataRequest, DownloadRequest, UploadRequest StreamRequest惑灵,并且他們都繼承自Request.這四個子類有一些方法,比如:authenticate, validate, responseJSON uploadProgress,這些方法的返回值又都是Self眼耀,這么做的目的是為了實現(xiàn)鏈?zhǔn)皆L問英支。

每一個請求都可以被暫停,恢復(fù)哮伟,和取消干花,分別使用下邊的方法:

  • suspend() 暫停
  • resume() 恢復(fù)妄帘, 在SessionManager中有一個屬性:startRequestsImmediately。他控制這請求是不是立刻發(fā)起池凄,默認(rèn)的值為true抡驼。
  • cancel() 取消 同時該請求的每一個監(jiān)聽對象都會受到一個錯誤回調(diào)

4.路由請求

Alamofire支持通過URLConvertibleURLRequestConvertible這兩個協(xié)議來實現(xiàn)路由設(shè)計模式,路由的概念就是中轉(zhuǎn)站的意思肿仑,在Alamofire中致盟,String, URL, URLComponents實現(xiàn)了URLConvertible協(xié)議。因此我們才能夠這么用:

let urlString = "https://httpbin.org/post"
Alamofire.request(urlString, method: .post)

let url = URL(string: urlString)!
Alamofire.request(url, method: .post)

let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)!
Alamofire.request(urlComponents, method: .post)

當(dāng)然我們也可以根據(jù)實際開發(fā)需求尤慰,來自定義符合我們需求的路由馏锡。在Alamofire的官方演示中,是這么使用的:

extension User: URLConvertible {
    static let baseURLString = "https://example.com"

    func asURL() throws -> URL {
        let urlString = User.baseURLString + "/users/\(username)/"
        return try urlString.asURL()
    }
}

上邊的代碼讓User實現(xiàn)了URLConvertible協(xié)議割择,因此我們就可以直接使用下邊的方式發(fā)起請求:

let user = User(username: "mattt")
Alamofire.request(user) // https://example.com/users/mattt

URLRequestConvertible的用法也很神奇眷篇,我們直接看例子:

enum Router: URLRequestConvertible {
    case search(query: String, page: Int)

    static let baseURLString = "https://example.com"
    static let perPage = 50

    // MARK: URLRequestConvertible

    func asURLRequest() throws -> URLRequest {
        let result: (path: String, parameters: Parameters) = {
            switch self {
            case let .search(query, page) where page > 0:
                return ("/search", ["q": query, "offset": Router.perPage * page])
            case let .search(query, _):
                return ("/search", ["q": query])
            }
        }()

        let url = try Router.baseURLString.asURL()
        let urlRequest = URLRequest(url: url.appendingPathComponent(result.path))

        return try URLEncoding.default.encode(urlRequest, with: result.parameters)
    }
}

Router實現(xiàn)了URLRequestConvertible協(xié)議,因此我們就能夠使用下邊的這種方式請求數(shù)據(jù):

Alamofire.request(Router.search(query: "foo bar", page: 1)) // https://example.com/search?q=foo%20bar&offset=50

上邊的Router就實現(xiàn)了根據(jù)query和page來生成一個request的過程荔泳。大家仔細回味下上邊封裝的Router蕉饼,很有意思。

在看看下邊的這個封裝:

import Alamofire

enum Router: URLRequestConvertible {
    case createUser(parameters: Parameters)
    case readUser(username: String)
    case updateUser(username: String, parameters: Parameters)
    case destroyUser(username: String)

    static let baseURLString = "https://example.com"

    var method: HTTPMethod {
        switch self {
        case .createUser:
            return .post
        case .readUser:
            return .get
        case .updateUser:
            return .put
        case .destroyUser:
            return .delete
        }
    }

    var path: String {
        switch self {
        case .createUser:
            return "/users"
        case .readUser(let username):
            return "/users/\(username)"
        case .updateUser(let username, _):
            return "/users/\(username)"
        case .destroyUser(let username):
            return "/users/\(username)"
        }
    }

    // MARK: URLRequestConvertible

    func asURLRequest() throws -> URLRequest {
        let url = try Router.baseURLString.asURL()

        var urlRequest = URLRequest(url: url.appendingPathComponent(path))
        urlRequest.httpMethod = method.rawValue

        switch self {
        case .createUser(let parameters):
            urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters)
        case .updateUser(_, let parameters):
            urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters)
        default:
            break
        }

        return urlRequest
    }
}

上邊的代碼把對User的操作進行了封裝玛歌,因此我們在操作User的時候昧港,不需要跟底層的數(shù)據(jù)打交道,按照這種設(shè)計寫出的代碼也更簡潔和具有可讀性支子。

Alamofire.request(Router.readUser("mattt")) // GET https://example.com/users/mattt

5.請求的適配和重試

Alampfire提供了RequestAdapterRequestRetrier這兩個協(xié)議來進行請求適配和重試的创肥。

RequestAdapter協(xié)議允許開發(fā)者改變request,這在實際應(yīng)用中值朋,會有很多實用場景叹侄,比如給請求中添加某個header:

class AccessTokenAdapter: RequestAdapter {
    private let accessToken: String

    init(accessToken: String) {
        self.accessToken = accessToken
    }

    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var urlRequest = urlRequest

        if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix("https://httpbin.org") {
            urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
        }

        return urlRequest
    }
}

當(dāng)AccessTokenAdapter成為某個SessionManager的適配者之后,SessionManager的每一個請求都會被這個AccessTokenAdapter適配一遍昨登。具體的代碼實現(xiàn)邏輯會在后續(xù)的章節(jié)中給出趾代。那么到這里,我們已經(jīng)掌握了好幾種添加headers得到方法了丰辣。AccessTokenAdapter的使用方法:

let sessionManager = SessionManager()
sessionManager.adapter = AccessTokenAdapter(accessToken: "1234")

sessionManager.request("https://httpbin.org/get")

關(guān)于RequestAdapterRequestRetrier的綜合運用撒强,Alamofire給出了一個一個這樣的例子:

class OAuth2Handler: RequestAdapter, RequestRetrier {
    private typealias RefreshCompletion = (_ succeeded: Bool, _ accessToken: String?, _ refreshToken: String?) -> Void

    private let sessionManager: SessionManager = {
        let configuration = URLSessionConfiguration.default
        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders

        return SessionManager(configuration: configuration)
    }()

    private let lock = NSLock()

    private var clientID: String
    private var baseURLString: String
    private var accessToken: String
    private var refreshToken: String

    private var isRefreshing = false
    private var requestsToRetry: [RequestRetryCompletion] = []

    // MARK: - Initialization

    public init(clientID: String, baseURLString: String, accessToken: String, refreshToken: String) {
        self.clientID = clientID
        self.baseURLString = baseURLString
        self.accessToken = accessToken
        self.refreshToken = refreshToken
    }

    // MARK: - RequestAdapter

    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) {
            var urlRequest = urlRequest
            urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
            return urlRequest
        }

        return urlRequest
    }

    // MARK: - RequestRetrier

    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
        lock.lock() ; defer { lock.unlock() }

        if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
            requestsToRetry.append(completion)

            if !isRefreshing {
                refreshTokens { [weak self] succeeded, accessToken, refreshToken in
                    guard let strongSelf = self else { return }

                    strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }

                    if let accessToken = accessToken, let refreshToken = refreshToken {
                        strongSelf.accessToken = accessToken
                        strongSelf.refreshToken = refreshToken
                    }

                    strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
                    strongSelf.requestsToRetry.removeAll()
                }
            }
        } else {
            completion(false, 0.0)
        }
    }

    // MARK: - Private - Refresh Tokens

    private func refreshTokens(completion: @escaping RefreshCompletion) {
        guard !isRefreshing else { return }

        isRefreshing = true

        let urlString = "\(baseURLString)/oauth2/token"

        let parameters: [String: Any] = [
            "access_token": accessToken,
            "refresh_token": refreshToken,
            "client_id": clientID,
            "grant_type": "refresh_token"
        ]

        sessionManager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default)
            .responseJSON { [weak self] response in
                guard let strongSelf = self else { return }

                if 
                    let json = response.result.value as? [String: Any], 
                    let accessToken = json["access_token"] as? String, 
                    let refreshToken = json["refresh_token"] as? String 
                {
                    completion(true, accessToken, refreshToken)
                } else {
                    completion(false, nil, nil)
                }

                strongSelf.isRefreshing = false
            }
    }
}

我們把上邊的代碼拆解成以下的使用場景:

  • 客戶端發(fā)送的每一個請求都要包含一個token,這個token很可能會過期笙什,過期的token不能使用飘哨,因此通過adapt方法把token添加到請求的header中
  • 當(dāng)使用現(xiàn)有的token請求失敗后,如果是token過期導(dǎo)致的請求失敗琐凭,那么就通過should方法重新申請一個新的token

使用方法:

let baseURLString = "https://some.domain-behind-oauth2.com"

let oauthHandler = OAuth2Handler(
    clientID: "12345678",
    baseURLString: baseURLString,
    accessToken: "abcd1234",
    refreshToken: "ef56789a"
)

let sessionManager = SessionManager()
sessionManager.adapter = oauthHandler
sessionManager.retrier = oauthHandler

let urlString = "\(baseURLString)/some/endpoint"

sessionManager.request(urlString).validate().responseJSON { response in
    debugPrint(response)
}

6.自定義響應(yīng)序列者

關(guān)于Alamofire中自定義序列響應(yīng)者芽隆。Alamofire已經(jīng)為我們提供了Data,JSON,strings和property lists的解析摆马。為了演示自定義的功能臼闻,我們要完成一下兩件事:

  • 為Alamofire擴展一個XML的解析
  • 直接把服務(wù)器返回的數(shù)據(jù)解析成對象,比方說User

為Alamofire擴展一個XML的解析

在做任何事情事前囤采,都應(yīng)該先設(shè)計好錯誤處理方案:

enum BackendError: Error {
    case network(error: Error) // Capture any underlying Error from the URLSession API
    case dataSerialization(error: Error)
    case jsonSerialization(error: Error)
    case xmlSerialization(error: Error)
    case objectSerialization(reason: String)
}

XML解析:

extension DataRequest {
    static func xmlResponseSerializer() -> DataResponseSerializer<ONOXMLDocument> {
        return DataResponseSerializer { request, response, data, error in
            // Pass through any underlying URLSession error to the .network case.
            guard error == nil else { return .failure(BackendError.network(error: error!)) }

            // Use Alamofire's existing data serializer to extract the data, passing the error as nil, as it has
            // already been handled.
            let result = Request.serializeResponseData(response: response, data: data, error: nil)

            guard case let .success(validData) = result else {
                return .failure(BackendError.dataSerialization(error: result.error! as! AFError))
            }

            do {
                let xml = try ONOXMLDocument(data: validData)
                return .success(xml)
            } catch {
                return .failure(BackendError.xmlSerialization(error: error))
            }
        }
    }

    @discardableResult
    func responseXMLDocument(
        queue: DispatchQueue? = nil,
        completionHandler: @escaping (DataResponse<ONOXMLDocument>) -> Void)
        -> Self
    {
        return response(
            queue: queue,
            responseSerializer: DataRequest.xmlResponseSerializer(),
            completionHandler: completionHandler
        )
    }
}

可以看出,這個解析是在DataRequest基礎(chǔ)上進行擴展的惩淳,當(dāng)然也可以在DownloadRequest上擴展蕉毯,xmlResponseSerializer函數(shù)的返回值是一個函數(shù),這種處理方式在Alamofire中經(jīng)常出現(xiàn)思犁,完全可以把函數(shù)當(dāng)成一種數(shù)據(jù)來對待代虾。response函數(shù)會把這個閉包函數(shù)加入到task代理的隊列中,在請求完成后會被調(diào)用激蹲,總之棉磨,這是一系列的過程,我會在后續(xù)的文章中詳細說明学辱。

- 直接把服務(wù)器返回的數(shù)據(jù)解析成對象乘瓤,比方說User

在開發(fā)中,能夠直接把服務(wù)器返回的數(shù)據(jù)轉(zhuǎn)換成對象還是很有價值的策泣。接下來我們看看用代碼是如何實現(xiàn)的:

protocol ResponseObjectSerializable {
    init?(response: HTTPURLResponse, representation: Any)
}

extension DataRequest {
    func responseObject<T: ResponseObjectSerializable>(
        queue: DispatchQueue? = nil,
        completionHandler: @escaping (DataResponse<T>) -> Void)
        -> Self
    {
        let responseSerializer = DataResponseSerializer<T> { request, response, data, error in
            guard error == nil else { return .failure(BackendError.network(error: error!)) }

            let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
            let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)

            guard case let .success(jsonObject) = result else {
                return .failure(BackendError.jsonSerialization(error: result.error!))
            }

            guard let response = response, let responseObject = T(response: response, representation: jsonObject) else {
                return .failure(BackendError.objectSerialization(reason: "JSON could not be serialized: \(jsonObject)"))
            }

            return .success(responseObject)
        }

        return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler)
    }
}

ResponseObjectSerializable這個協(xié)議是關(guān)鍵衙傀,這個協(xié)議提供了一個初始化方法,方法的參數(shù)有兩個萨咕,一個是服務(wù)器返回的響應(yīng)统抬,另一個是被轉(zhuǎn)化后的數(shù)據(jù),著這個例子中使用的是JSON危队。也就是說對象一定要實現(xiàn)這個協(xié)議聪建,在這個協(xié)議方法中拿到這兩個參數(shù),然后給自己的屬性賦值就可以了 茫陆。

User的代碼:

struct User: ResponseObjectSerializable, CustomStringConvertible {
    let username: String
    let name: String

    var description: String {
        return "User: { username: \(username), name: \(name) }"
    }

    init?(response: HTTPURLResponse, representation: Any) {
        guard
            let username = response.url?.lastPathComponent,
            let representation = representation as? [String: Any],
            let name = representation["name"] as? String
        else { return nil }

        self.username = username
        self.name = name
    }
}

使用方法:

Alamofire.request("https://example.com/users/mattt").responseObject { (response: DataResponse<User>) in
    debugPrint(response)

    if let user = response.result.value {
        print("User: { username: \(user.username), name: \(user.name) }")
    }
}

Alamofire的文檔中還掩飾了一個系列成[User]的例子金麸,由于篇幅的原因,在這里就不解釋了盅弛。

7.安全

Alamofire中關(guān)于安全策略的使用钱骂,會放到后邊的文章中介紹。

8.網(wǎng)絡(luò)狀態(tài)監(jiān)控

主要用于實時監(jiān)控當(dāng)前的網(wǎng)絡(luò)情況

let manager = NetworkReachabilityManager(host: "www.apple.com")

manager?.listener = { status in
    print("Network Status Changed: \(status)")
}

manager?.startListening()

有一下幾點值得注意:

  • 不要用該監(jiān)控來決定是不是發(fā)送請求挪鹏,應(yīng)該直接發(fā)送
  • 當(dāng)網(wǎng)絡(luò)恢復(fù)之后见秽,嘗試重新發(fā)送請求
  • 狀態(tài)嗎可以用來查看網(wǎng)絡(luò)問題的原因

總結(jié)

以上就是本篇的所有內(nèi)容,知識大概的講解了Alamofire的使用技巧讨盒,真正能夠提高代碼水平的源碼解讀解取,我會盡量完成。

如果有任何錯誤之處返顺,歡迎提出禀苦,多謝了蔓肯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市振乏,隨后出現(xiàn)的幾起案子蔗包,更是在濱河造成了極大的恐慌,老刑警劉巖慧邮,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件调限,死亡現(xiàn)場離奇詭異,居然都是意外死亡误澳,警方通過查閱死者的電腦和手機耻矮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忆谓,“玉大人裆装,你說我怎么就攤上這事〕” “怎么了哨免?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長毡琉。 經(jīng)常有香客問我铁瞒,道長,這世上最難降的妖魔是什么桅滋? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任慧耍,我火速辦了婚禮,結(jié)果婚禮上丐谋,老公的妹妹穿的比我還像新娘芍碧。我一直安慰自己,他們只是感情好号俐,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布泌豆。 她就那樣靜靜地躺著,像睡著了一般吏饿。 火紅的嫁衣襯著肌膚如雪踪危。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天猪落,我揣著相機與錄音贞远,去河邊找鬼。 笑死笨忌,一個胖子當(dāng)著我的面吹牛蓝仲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼袱结,長吁一口氣:“原來是場噩夢啊……” “哼亮隙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起垢夹,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤溢吻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后果元,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體煤裙,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年噪漾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片且蓬。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡欣硼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恶阴,到底是詐尸還是另有隱情诈胜,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布冯事,位于F島的核電站焦匈,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏昵仅。R本人自食惡果不足惜缓熟,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望摔笤。 院中可真熱鬧够滑,春花似錦、人聲如沸吕世。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽命辖。三九已至况毅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尔艇,已是汗流浹背尔许。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留漓帚,地道東北人母债。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親毡们。 傳聞我的和親對象是個殘疾皇子迅皇,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容