Alamofire 5 的使用 - 基本用法

即將離開簡書,請到掘金繼續關注我。謝謝!

本文掘金鏈接

為什么離開

此文章是對 Alamofire Usage 的翻譯,有需要的可以去看原文。

另外此文章的內容也保存到了我的 GitHub 倉庫,建議去 GitHub 閱讀,以獲得更好的閱讀體驗。如果覺得對你有用的,可以順手給個 Star。謝謝!

Alamofire 5 的使用 - 基本用法

我分兩篇文章介紹如何使用 Alamofire 5。文章的內容主要是翻譯 Alamofire 的 readme。第二篇文章 >>

特性

  • 可鏈接的請求/響應函數
  • URL / JSON 參數編碼
  • 上傳文件 / Data / 流 / 多表單數據
  • 使用請求或者恢復數據下載文件
  • 使用 URLCredential 進行身份驗證
  • HTTP 響應驗證
  • 帶有進度的上傳和下載閉包
  • cURL 命令的輸出
  • 動態調整和重試請求
  • TLS 證書和公鑰固定
  • 網絡可達性
  • 全面的單元和集成測試覆蓋率

組件庫

為了讓 Alamofire 專注于核心網絡實現,Alamofire 軟件基金會創建了額外的組件庫,為 Alamofire 生態系統帶來額外的功能。

  • AlamofireImage:一個圖片庫,包括圖像響應序列化器、UIImageUIImageView 的擴展、自定義圖像濾鏡、自動清除內存緩存和基于優先級的圖像下載系統。
  • AlamofireNetworkActivityIndicator: 使用 Alamofire 控制 iOS 上網絡活動指示器的可見性。它包含可配置的延遲計時器,有助于減少閃爍,并且可以支持不由 Alamofire 管理的 URLSession 實例。

要求的使用環境

  • iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+
  • Xcode 10.2+
  • Swift 5+

安裝方法

CocoaPods

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!

target '項目名稱' do
    pod 'Alamofire', '~> 5.0'
end

iOS 版本和 Alamofire 版本可以自己根據實際情況自行更改。CocoaPods 是比較常用的第三方庫管理工具,其他方法就不詳細說了。其他集成方法可以查看原文檔

基本用法

介紹

Alamofire 為 HTTP 網絡請求提供了一個優雅且可組合的接口。它沒有實現自己的 HTTP 網絡功能。取而代之的是,它建立在由 Foundation 框架提供的 URL 加載系統之上。系統的核心是 URLSessionURLSessionTask 子類。Alamofire 將這些 APIs 和許多其他 APIs 封裝在一個更易于使用的接口中,并提供使用 HTTP 網絡進行現代應用程序開發所必需的各種功能。但是,了解 Alamofire 的許多核心行為來自何處很重要,因此熟悉 URL 加載系統非常重要。歸根結底,Alamofire 的網絡特性受到該系統功能的限制,應該始終記住并遵守其行為和最佳實踐。

此外,Alamofire(以及 URL 加載系統)中的聯網是異步完成的。異步編程可能會讓不熟悉這個概念的程序員感到沮喪,但是有很好的理由這樣做。

另外:AF 命名空間和引用

以前的 Alamofire 文檔使用了類似 Alamofire.request() 的示例。這個 API 雖然看起來需要 Alamofire 前綴,但實際上在沒有它的情況下也可以。request 方法和其他函數在任何帶有 import Alamofire 的文件中都是全局可用的。從 Alamofire 5 開始,此功能已被刪除,被更改為 AF ,它是對 Session.default 的引用。這允許 Alamofire 提供同樣的便利功能,同時不必每次使用 Alamofire 時都污染全局命名空間,也不必全局復制 Session API。類似地,由 Alamofire 擴展的類型將使用 af 屬性擴展來將 Alamofire 添加的功能與其他擴展分開。

發起請求

Alamofire 為發出 HTTP 請求提供了多種方便的方法。最簡單的是,只需提供一個可以轉換為 URL 的 String

AF.request("https://httpbin.org/get").response { response in
    debugPrint(response)
}

所有示例都需要在源文件中的某個位置 import Alamofire

這實際上是 Alamofire Session 類型上用于發出請求的兩個頂層 APIs 的一種形式。它的完整定義如下:

open func request<Parameters: Encodable>(
    _ convertible: URLConvertible,
    method: HTTPMethod = .get,
    parameters: Parameters? = nil,
    encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
    headers: HTTPHeaders? = nil,
    interceptor: RequestInterceptor? = nil
) -> DataRequest

此方法創建一個 DataRequest,同時允許組合來自各個組件(如 methodheaders )的請求,同時還允許每個傳入 RequestInterceptorsEncodable 參數。

還有其他方法允許您使用 Parameters 字典和 ParameterEncoding 類型來發出請求。不再推薦此 API,最終將被棄用并從 Alamofire 中刪除。

這個 API 的第二個版本要簡單得多:

open func request(
    _ urlRequest: URLRequestConvertible,
    interceptor: RequestInterceptor? = nil
) -> DataRequest

此方法為遵循 AlamofireURLRequestConvertible 協議的任何類型創建 DataRequest 。所有不同于前一版本的參數都封裝在該值中,這會產生非常強大的抽象。這將在我們的高級用法中討論。

HTTP Methods

HTTPMethod 類型列出了 RFC 7231 §4.3 中定義的 HTTP 方法:

public struct HTTPMethod: RawRepresentable, Equatable, Hashable {
    public static let connect = HTTPMethod(rawValue: "CONNECT")
    public static let delete = HTTPMethod(rawValue: "DELETE")
    public static let get = HTTPMethod(rawValue: "GET")
    public static let head = HTTPMethod(rawValue: "HEAD")
    public static let options = HTTPMethod(rawValue: "OPTIONS")
    public static let patch = HTTPMethod(rawValue: "PATCH")
    public static let post = HTTPMethod(rawValue: "POST")
    public static let put = HTTPMethod(rawValue: "PUT")
    public static let trace = HTTPMethod(rawValue: "TRACE")

    public let rawValue: String

    public init(rawValue: String) {
        self.rawValue = rawValue
    }
}

這些值可以作為 method 參數傳遞給 AF.request API:

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

重要的是要記住,不同的 HTTP 方法可能有不同的語義,需要不同的參數編碼,這取決于服務器的期望。例如,URLSession 或 Alamofire 不支持在 GET 請求中傳遞 body 數據,并將返回錯誤。

Alamofire 還提供了對 URLRequest 的擴展,以橋接將字符串返回到 HTTPMethod 值的 httpMethod 屬性:

public extension URLRequest {
    /// Returns the `httpMethod` as Alamofire's `HTTPMethod` type.
    var method: HTTPMethod? {
        get { return httpMethod.flatMap(HTTPMethod.init) }
        set { httpMethod = newValue?.rawValue }
    }
}

如果需要使用 Alamofire 的 HTTPMethod 類型不支持的 HTTP 方法,可以擴展該類型以添加自定義值:

extension HTTPMethod {
    static let custom = HTTPMethod(rawValue: "CUSTOM")
}

請求參數和參數編碼器

Alamofire 支持將任何 Encodable 類型作為請求的參數。然后,這些參數通過遵循 ParameterEncoder 協議的類型傳遞,并添加到 URLRequest 中,然后通過網絡發送。Alamofire 包含兩種遵循 ParameterEncoder 的類型:JSONParameterEncoderURLEncodedFormParameterEncoder 。這些類型涵蓋了現代服務使用的最常見的編碼。

struct Login: Encodable {
    let email: String
    let password: String
}

let login = Login(email: "test@test.test", password: "testPassword")

AF.request("https://httpbin.org/post",
           method: .post,
           parameters: login,
           encoder: JSONParameterEncoder.default).response { response in
    debugPrint(response)
}

URLEncodedFormParameterEncoder

URLEncodedFormParameterEncoder 將值編碼為 URL 編碼字符串,以將其設置為或附加到任何現有 URL 查詢字符串,或設置為請求的 HTTP body。通過設置編碼的目的地,可以控制編碼字符串的設置位置。URLEncodedFormParameterEncoder.Destination 枚舉有三種情況:

  • .methodDependent - 對于 .get.head.delete 請求,它會將已編碼查詢字符串應用到現有的查詢字符串中;對于其他類型的請求,會將其設置為 HTTP body。
  • .queryString - 將編碼字符串設置或追加到請求的 URL 中。
  • .httpBody - 將編碼字符串設置為 URLRequest 的 HTTP body。

如果尚未設置 Content-Type,那么會把具有 HTTP body 的已編碼請求的 HTTP header 設置為 application/x-www-form-urlencoded; charset=utf-8

在內部,URLEncodedFormParameterEncoder 使用 URLEncodedFormEncoderEncodable 類型編碼為 URL 編碼形式的 String。此編碼器可用于自定義各種類型的編碼,包括使用 ArrayEncodingArray、使用 BoolEncodingBool、使用 DataEncodingData、使用 DateEncodingDate、使用 KeyEncoding 的 keys 以及使用 SpaceEncoding 的空格。

使用 URL 編碼參數的 GET 請求
let parameters = ["foo": "bar"]

// 下面三種方法都是等價的
AF.request("https://httpbin.org/get", parameters: parameters) // encoding defaults to `URLEncoding.default`
AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder.default)
AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder(destination: .methodDependent))

// https://httpbin.org/get?foo=bar
使用 URL 編碼參數的 POST 請求
let parameters: [String: [String]] = [
    "foo": ["bar"],
    "baz": ["a", "b"],
    "qux": ["x", "y", "z"]
]

// 下面三種方法都是等價的
AF.request("https://httpbin.org/post", method: .post, parameters: parameters)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: URLEncodedFormParameterEncoder.default)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: URLEncodedFormParameterEncoder(destination: .httpBody))

// HTTP body: "qux[]=x&qux[]=y&qux[]=z&baz[]=a&baz[]=b&foo[]=bar"

配置已編碼參數的排序

從 Swift 4.2 開始,Swift 的 Dictionary 類型使用的隨機算法在運行時產生一個隨機的內部順序,并且在應用程序的每次啟動都是不同的。這可能會導致已編碼參數更改順序,這可能會影響緩存和其他行為。默認情況下,URLEncodedFormEncoder 將對其編碼的鍵值對進行排序。雖然這會為所有 Encodable 類型生成常量輸出,但它可能與該類型實現的實際編碼順序不匹配。您可以將 alphabetizeKeyValuePairs 設置為 false 以返回到實現的順序,因此這將變成隨機 Dictionary 順序。

您可以創建自己的 URLEncodedFormParameterEncoder,在初始化時,可以在 URLEncodedFormEncoder 參數中設置 alphabetizeKeyValuePairs 的值:

let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(alphabetizeKeyValuePairs: false))
配置 Array 參數的編碼

由于沒有關于如何對集合類型進行編碼的規范,默認情況下,Alamofire 遵循以下約定:將 [] 附加到數組值的鍵(foo[]=1&foo[]=2),并附加由中括號包圍的嵌套字典值的鍵(foo[bar]=baz)。

URLEncodedFormEncoder.ArrayEncoding 枚舉提供了以下對數組參數進行編碼的方法:

  • .brackets - 為每個值在鍵后附加一組空的中括號。這是默認情況。
  • .noBrackets - 不附加括號。key 按原樣編碼。

默認情況下,Alamofire 使用 .brackets 編碼,其中 foo = [1, 2] 編碼為 foo[]=1&foo[]=2

使用 .noBrackets 編碼,foo = [1, 2] 編碼為 foo=1&foo=2

您可以創建自己的 URLEncodedFormParameterEncoder,在初始化時,可以在 URLEncodedFormEncoder 參數中設置 arrayEncoding 的值:

let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(arrayEncoding: .noBrackets))
配置 Bool 參數的編碼

URLEncodedFormEncoder.BoolEncoding 枚舉提供了以下用于編碼 Bool 參數的方法:

  • .numeric - 把 true 編碼為 1false 編碼為 0。這是默認情況。
  • .literal - 把 truefalse 編碼為字符串文本。

默認情況下,Alamofire 使用 .numeric

您可以創建自己的 URLEncodedFormParameterEncoder,在初始化時,可以在 URLEncodedFormEncoder 參數中設置 boolEncoding 的值:

let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(boolEncoding: .numeric))
配置 Data 參數的編碼

DataEncoding 包括以下用于編碼 Data 參數的方法:

  • .deferredToData - 使用 Data 的自帶 Encodable 支持。
  • .base64 - 將 Data 編碼為 base64 編碼的字符串。這是默認情況。
  • .custom((Data) -> throws -> String) - 使用給定的閉包對 Data 進行編碼。

您可以創建自己的 URLEncodedFormParameterEncoder,在初始化時,可以在 URLEncodedFormEncoder 參數中設置 dataEncoding 的值:

let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(dataEncoding: .base64))
配置 Date 參數的編碼

鑒于將 Date 編碼為 String 的方法非常多,DateEncoding 包括以下用于編碼 Date 參數的方法:

  • .deferredToDate - 使用 Date 的自帶 Encodable 支持。這是默認情況。
  • .secondsSince1970 - 將 Date 編碼為 1970 年 1 月 1 日 UTC 零點的秒數。
  • .millisecondsSince1970 - 將 Date 編碼為 1970 年 1 月 1 日 UTC 零點的毫秒數。
  • .iso8601 - 根據 ISO 8601 和 RFC3339 標準對 Date 進行編碼。
  • .formatted(DateFormatter) - 使用給定的 DateFormatterDate 進行編碼。
  • .custom((Date) throws -> String) - 使用給定的閉包對 Date 進行編碼。

您可以創建自己的 URLEncodedFormParameterEncoder,在初始化時,可以在 URLEncodedFormEncoder 參數中設置 dateEncoding 的值:

let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(dateEncoding: .iso8601))
配置 Coding Keys 的編碼

由于 key 參數樣式的多樣性,KeyEncoding 提供了以下方法來從 lowerCamelCase 中自定義 key 編碼:

  • .useDefaultKeys - 使用每種類型指定的 key。這是默認情況。
  • .convertToSnakeCase - 將 key 轉換為 snake case:oneTwoThree 變成 one_two_three
  • .convertToKebabCase - 將 key 轉換為 kebab case:oneTwoThree 變成 one-two-three
  • .capitalized - 將第一個字母大寫,例如 oneTwoThree 變為 OneTwoThree
  • .uppercased - 所有字母大寫:oneTwoThree 變成 ONETWOTHREE
  • .lowercased - 所有字母小寫:oneTwoThree 變成 onetwothree
  • .custom((String) -> String) - 使用給定的閉包對 key 進行編碼。

您可以創建自己的 URLEncodedFormParameterEncoder,在初始化時,可以在 URLEncodedFormEncoder 參數中設置 keyEncoding 的值:

let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(keyEncoding: .convertToSnakeCase))
配置空格的編碼

舊的表單編碼器使用 + 來對空格進行編碼,而一些服務器仍然希望使用這種編碼,而不是現代的百分比編碼,因此 Alamofire 包含以下對空格進行編碼的方法:

.percentEscaped - 通過應用標準百分比轉義對空格字符進行編碼。" " 編碼為"%20"。這是默認情況。
.plusReplaced - 將空格字符替換為 +" " 編碼為"+"

您可以創建自己的 URLEncodedFormParameterEncoder,在初始化時,可以在 URLEncodedFormEncoder 參數中設置 spaceEncoding 的值:

let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(spaceEncoding: .plusReplaced))

JSONParameterEncoder

JSONParameterEncoder 使用 Swift 的 JSONEncoderEncodable 值進行編碼,并將結果設置為 URLRequesthttpBody。如果 Content-Type 尚未設置,則將其設置為 application/json

JSON 編碼參數的 POST 請求
let parameters: [String: [String]] = [
    "foo": ["bar"],
    "baz": ["a", "b"],
    "qux": ["x", "y", "z"]
]

AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: JSONParameterEncoder.default)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: JSONParameterEncoder.prettyPrinted)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: JSONParameterEncoder.sortedKeys)

// HTTP body: {"baz":["a","b"],"foo":["bar"],"qux":["x","y","z"]}
自定義 JSONEncoder

您可以自定義 JSONParameterEncoder 的行為,方法是將自定義的 JSONEncoder 實例傳遞給它:

let encoder = JSONEncoder()
encoder.dateEncoding = .iso8601
encoder.keyEncodingStrategy = .convertToSnakeCase
let parameterEncoder = JSONParameterEncoder(encoder: encoder)
手動對 URLRequest 進行參數編碼

ParameterEncoder APIs 也可以在 Alamofire 之外使用,方法是直接在URLRequest 中編碼參數。

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

let parameters = ["foo": "bar"]
let encodedURLRequest = try URLEncodedFormParameterEncoder.default.encode(parameters, into: urlRequest)

HTTP Headers

Alamofire 包含自己的 HTTPHeaders 類型,這是一種順序保持且不區分大小寫的 HTTP header name/value 對的表示。HTTPHeader 類型封裝單個 name/value 對,并為常用的 headers 提供各種靜態值。

Request 添加自定義 HTTPHeaders 就像向 request 方法傳遞值一樣簡單:

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

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

HTTPHeaders 也可以由 HTTPHeader 數組構造:

let headers: HTTPHeaders = [
    .authorization(username: "Username", password: "Password"),
    .accept("application/json")
]

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

對于不會變的 HTTP headers,建議在 URLSessionConfiguration 上設置它們,以便讓它們自動應用于底層 URLSession 創建的任何 URLSessionTask

默認的 Alamofire Session 為每個 Request 提供一組默認的 headers。其中包括:

  • Accept-Encoding,默認為 br;q=1.0, gzip;q=0.8, deflate;q=0.6,根據 RFC 7230 §4.2.3
  • Accept-Language,默認為系統中最多 6 種首選語言,格式為 en;q=1.0,根據 RFC 7231 §5.3.5
  • User-Agent,其中包含有關當前應用程序的版本信息。例如:iOS Example/1.0 (com.alamofire.iOS-Example; build:1; iOS 13.0.0) Alamofire/5.0.0,根據 RFC 7231 §5.5.3

如果需要自定義這些 headers,則應創建自定義 URLSessionConfiguration,更新 defaultHTTPHeaders 屬性,并將配置應用于新 Session 實例。使用URLSessionConfiguration.af.default 來自定義配置,會保留 Alamofire 的默認 headers。

響應驗證

默認情況下,無論響應的內容如何,Alamofire 都會將任何已完成的請求視為成功。如果響應具有不可接受的狀態代碼或 MIME 類型,則在響應處理程序之前調用 validate() 將導致生成錯誤。

自動驗證

validate() API 自動驗證狀態代碼是否在 200..<300 范圍內,以及響應的 Content-Type header 是否與請求的 Accept 匹配(如果有提供)。

AF.request("https://httpbin.org/get").validate().responseJSON { response in
    debugPrint(response)
}

手動驗證

AF.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 let .failure(error):
            print(error)
        }
    }

響應處理

Alamofire 的 DataRequestDownloadRequest 都有相應的響應類型:DataResponse<Success, Failure: Error>DownloadResponse<Success, Failure: Error>。這兩個類型都由兩個泛型組成:序列化類型和錯誤類型。默認情況下,所有響應值都將生成 AFError 錯誤類型(DataResponse<Success, AFError>)。Alamofire 在其公共 API 中使用了更簡單的 AFDataResponse<Success>AFDownloadResponse<Success>,它們總是有 AFError 錯誤類型。UploadRequestDataRequest 的一個子類,使用相同的 DataResponse 類型。

處理在 Alamofire 中發出的 DataRequestUploadRequestDataResponse 涉及到鏈接 response handler,例如 responseJSON 鏈接到 DataRequest:

AF.request("https://httpbin.org/get").responseJSON { response in
    debugPrint(response)
}

在上面的示例中,responseJSON handler 被添加到 DataRequest 中,以便在 DataRequest 完成后執行。傳遞給 handler 閉包的參數是從響應屬性來的 JSONResponseSerializer 生成的 AFDataResponse<Any> 值。

此閉包并不阻塞執行以等待服務器的響應,而是作為回調添加,以便在收到響應后處理該響應。請求的結果僅在響應閉包的范圍內可用。任何依賴于從服務器接收到的響應或數據的執行都必須在響應閉包中完成。

Alamofire 的網絡請求是異步完成的。異步編程可能會讓不熟悉這個概念的程序員感到沮喪,但是有很好的理由這樣做。

默認情況下,Alamofire 包含六個不同的數據響應 handlers,包括:


// Response Handler - 未序列化的 Response
func response(
    queue: DispatchQueue = .main,
    completionHandler: @escaping (AFDataResponse<Data?>) -> Void
) -> Self

// Response Serializer Handler - Serialize using the passed Serializer
func response<Serializer: DataResponseSerializerProtocol>(
    queue: DispatchQueue = .main,
    responseSerializer: Serializer,
    completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject>) -> Void
) -> Self

// Response Data Handler - Serialized into Data
func responseData(
    queue: DispatchQueue = .main,
    completionHandler: @escaping (AFDataResponse<Data>) -> Void
) -> Self

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

// Response JSON Handler - Serialized into Any Using JSONSerialization
func responseJSON(
    queue: DispatchQueue = .main,
    options: JSONSerialization.ReadingOptions = .allowFragments,
    completionHandler: @escaping (AFDataResponse<Any>) -> Void
) -> Self

// Response Decodable Handler - Serialized into Decodable Type
func responseDecodable<T: Decodable>(
    of type: T.Type = T.self,
    queue: DispatchQueue = .main,
    decoder: DataDecoder = JSONDecoder(),
    completionHandler: @escaping (AFDataResponse<T>) -> Void
) -> Self

沒有一個響應 handlers 對從服務器返回的 HTTPURLResponse 執行任何驗證。

例如,400..<500500..<600 范圍內的響應狀態代碼不會自動觸發錯誤。Alamofire 使用響應驗證鏈接來實現這一點。

響應 Handler

響應 handler 不計算任何響應數據。它只是直接從 URLSessionDelegate 轉發所有信息。它相當于使用 cURL 執行請求。

AF.request("https://httpbin.org/get").response { response in
    debugPrint("Response: \(response)")
}

我們強烈建議您利用 ResponseResult 類型來利用其他響應序列化器。

響應 Data Handler

responseData handler 使用 DataResponseSerializer 提取并驗證服務器返回的數據。如果沒有發生錯誤并且返回數據,則響應結果將為 .successvalue 將為從服務器返回的 Data

AF.request("https://httpbin.org/get").responseData { response in
    debugPrint("Response: \(response)")
}

響應 String Handler

responseString handler 使用 StringResponseSerializer 將服務器返回的數據轉換為具有指定編碼的 String。如果沒有發生錯誤,并且服務器數據成功序列化為 String,則響應結果將為 .success,并且值的類型為 String

AF.request("https://httpbin.org/get").responseString { response in
    debugPrint("Response: \(response)")
}

如果未指定編碼,Alamofire 將使用服務器 HTTPURLResponse 中指定的文本編碼。如果服務器響應無法確定文本編碼,則默認為 .isoLatin1

響應 JSON Handler

responseJSON handler 使用 JSONResponseSerializer 使用指定的 JSONSerialization.ReadingOptions 將服務器返回的數據轉換為 Any 類型。如果沒有出現錯誤,并且服務器數據成功序列化為 JSON 對象,則響應 AFResult 將為 .success,值將為 Any 類型。

AF.request("https://httpbin.org/get").responseJSON { response in
    debugPrint("Response: \(response)")
}

響應 Decodable Handler

responseDecodable handler 使用 DecodableResponseSerializer 和 指定的 DataDecoderDecoder 的協議抽象,可以從 Data 解碼)將服務器返回的數據轉換為傳遞進來的 Decodable 類型。如果沒有發生錯誤,并且服務器數據已成功解碼為 Decodable 類型,則響應 Result 將為 .success,并且 value 將為傳遞進來的類型。

struct HTTPBinResponse: Decodable {
    let url: String
}

AF.request("https://httpbin.org/get").responseDecodable(of: HTTPBinResponse.self) { response in
    debugPrint("Response: \(response)")
}

鏈式響應 handlers

響應 handlers 還可以連接起來:

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

需要注意的是,對同一請求使用多個響應 handlers 需要多次序列化服務器數據,每個響應 handlers 處理一次。作為最佳實踐,通常應避免對同一請求使用多個響應 handlers,特別是在生產環境中。它們只能用于調試或在沒有更好選擇的情況下使用。

響應 Handler 隊列

默認情況下,傳遞給響應 handler 的閉包在 .main 隊列上執行,但可以傳遞一個指定的 DispatchQueue 來執行閉包。實際的序列化工作(將 Data 轉換為其他類型)總是在后臺隊列上執行。

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

AF.request("https://httpbin.org/get").responseJSON(queue: utilityQueue) { response in
    print("Executed on utility queue.")
    debugPrint(response)
}

響應緩存

響應緩存使用系統自帶的 URLCache 處理。它提供了內存和磁盤上的復合緩存,并允許您管理用于緩存的內存和磁盤的大小。

默認情況下,Alamofire 利用 URLCache.shared 實例。要自定義使用的URLCache 實例,請查看高級用法

身份驗證

身份驗證使用系統自帶的 URLCredentialURLAuthenticationChallenge 處理。

這些身份驗證 APIs 用于提示授權的服務器,而不是一般用于需要身份驗證或等效的 header APIs。

支持的身份驗證方案

HTTP Basic 身份驗證

Requestauthenticate 方法將在使用 URLAuthenticationChallenge 進行質詢時自動提供 URLCredential(如果適用):

let user = "user"
let password = "password"

AF.request("https://httpbin.org/basic-auth/\(user)/\(password)")
    .authenticate(username: user, password: password)
    .responseJSON { response in
        debugPrint(response)
    }

使用 URLCredential 進行驗證

let user = "user"
let password = "password"

let credential = URLCredential(user: user, password: password, persistence: .forSession)

AF.request("https://httpbin.org/basic-auth/\(user)/\(password)")
    .authenticate(with: credential)
    .responseJSON { response in
        debugPrint(response)
    }

需要注意的是,當使用 URLCredential 進行身份驗證時,如果服務器發出質詢,底層 URLSession 實際上將發出兩個請求。第一個請求將不包括“可能”觸發服務器質詢的 credential。然后,Alamofire 接收質詢,追加 credential,并由底層 URLSession 重試請求。

手動驗證

如果您正在與始終需要 Authenticate 或類似 header 而不提示的 API 通信,則可以手動添加:

let user = "user"
let password = "password"

let headers: HTTPHeaders = [.authorization(username: user, password: password)]

AF.request("https://httpbin.org/basic-auth/user/password", headers: headers)
    .responseJSON { response in
        debugPrint(response)
    }

但是,必須是所有請求的一部分的 headers,通常作為自定義 URLSessionConfiguration 的一部分或通過使用 RequestAdapter 來更好地處理。

下載數據到文件中

除了將數據提取到內存中之外,Alamofire 還提供了 Session.downloadDownloadRequestDownloadResponse<Success,Failure:Error> 以方便下載數據到磁盤。雖然下載到內存中對小負載(如大多數 JSON API 響應)非常有用,但獲取更大的資源(如圖像和視頻)應下載到磁盤,以避免應用程序出現內存問題。

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

DownloadRequest 具有與 DataRequest 相同的大多數響應 handlers。但是,由于它將數據下載到磁盤,因此序列化響應涉及從磁盤讀取,還可能涉及將大量數據讀入內存。在設計下載處理時,記住這些事實是很重要的。

下載文件的存放位置

所有下載的數據最初都存儲在系統臨時目錄中。它最終會在將來的某個時候被系統刪除,所以如果它需要更長的壽命,將文件移到其他地方是很重要的。

您可以提供 Destination 閉包,將文件從臨時目錄移動到最終的存放位置。在臨時文件實際移動到 destinationURL 之前,將執行閉包中指定的 Options。當前支持的兩個 Options 是:

  • .createIntermediateDirectories - 如果指定,則為目標 URL 創建中間目錄。
  • .removePreviousFile - 如果指定,則從目標 URL 中刪除以前的文件。
let destination: DownloadRequest.Destination = { _, _ in
    let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    let fileURL = documentsURL.appendingPathComponent("image.png")

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

AF.download("https://httpbin.org/image/png", to: destination).response { response in
    debugPrint(response)

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

您還可以使用建議的 destination API:

let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)

AF.download("https://httpbin.org/image/png", to: destination)

下載進度

很多時候向用戶報告下載進度是有幫助的。任何 DownloadRequest 都可以使用 downloadProgress 報告下載進度。

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

URLSession 的進度報告 APIs(也是 Alamofire 的)只有在服務器正確返回可用于計算進度的 Content-Length header 時才能工作。如果沒有這個 header,進度將保持在 0.0,直到下載完成,此時進度將跳到 1.0

downloadProgress API 還可以接收一個 queue 參數,該參數定義應該對哪個 DispatchQueue 調用下載進度閉包。

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

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

取消和恢復下載

除了所有請求類都有 cancel() API 外,DownloadRequest 還可以生成恢復數據,這些數據可以用于以后恢復下載。此 API 有兩種形式:1)cancel(producingResumeData: Bool),它允許控制是否生成恢復數據,但僅在 DownloadResponse 可用;2)cancel(byProducingResumeData: (_ resumeData: Data?) -> Void),它執行相同的操作,但恢復數據在 completion handler 中可用。

如果 DownloadRequest 被取消或中斷,則底層的 URLSessionDownloadTask 可能會生成恢復數據。如果發生這種情況,可以重新使用恢復數據來重新啟動停止的 DownloadRequest

重要提示:在所有 Apple 平臺的某些版本(iOS 10 - 10.2、macOS 10.12 - 10.12.2、tvOS 10 - 10.1、watchOS 3 - 3.1.1)上,resumeData 在后臺 URLSessionConfiguration 上被破壞。resumeData 生成邏輯中存在一個潛在的錯誤,即數據寫入錯誤,并且總是無法恢復下載。有關此錯誤和可能的解決方法的詳細信息,請參閱此 Stack Overflow 的帖子

var resumeData: Data!

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

// download.cancel(producingResumeData: true) // Makes resumeData available in response only.
download.cancel { data in
    resumeData = data
}

AF.download(resumingWith: resumeData).responseData { response in
    if let data = response.value {
        let image = UIImage(data: data)
    }
}

上傳數據到服務器

當使用 JSON 或 URL 編碼的參數向服務器發送相對少量的數據時,request() 通常就足夠了。如果需要從內存、文件 URL 或 InputStream 中的 Data 發送大量數據,那么 upload() 就是您想要使用的。

上傳 Data

let data = Data("data".utf8)

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

上傳文件

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

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

上傳多表單數據

AF.upload(multipartFormData: { multipartFormData in
    multipartFormData.append(Data("one".utf8), withName: "one")
    multipartFormData.append(Data("two".utf8), withName: "two")
}, to: "https://httpbin.org/post")
    .responseJSON { response in
        debugPrint(response)
    }

上傳進度

當用戶等待上傳完成時,有時向用戶顯示上傳的進度會很方便。任何 UploadRequest 都可以使用 uploadProgressdownloadProgress 報告響應數據下載的上傳進度和下載進度。

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

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

統計指標

URLSessionTaskMetrics

Alamofire 為每個 Request 收集了 URLSessionTaskMetricsURLSessionTaskMetrics 封裝了一些關于底層網絡連接、請求和響應時間的奇妙統計信息。

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

cURL 的命令輸出

調試平臺問題可能令人沮喪。幸運的是,Alamofire 的 Request 類型可以生成等效的 cURL 命令,以便調試。由于 Alamofire Request 創建的異步性,這個 API 有同步和異步兩個版本。要盡快獲取 cURL 命令,可以將 cURLDescription 鏈接到請求上:

AF.request("https://httpbin.org/get")
    .cURLDescription { description in
        print(description)
    }
    .responseJSON { response in
        debugPrint(response.metrics)
    }

這將會生成:

$ curl -v \
-X GET \
-H "Accept-Language: en;q=1.0" \
-H "Accept-Encoding: br;q=1.0, gzip;q=0.9, deflate;q=0.8" \
-H "User-Agent: Demo/1.0 (com.demo.Demo; build:1; iOS 13.0.0) Alamofire/1.0" \
"https://httpbin.org/get"
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容