Alamofire 5 的使用 - 基本用法

Alamofire 5 的使用 - 基本用法

https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md

特性

可链接的请求/响应函数

URL / JSON 参数编码

上传文件/ Data / 流 / 多表单数据

使用请求或者恢复数据下载文件

使用URLCredential 进行身份验证

HTTP 响应验证

带有进度的上传和下载闭包

cURL 命令的输出

]动态调整和重试请求

TLS 证书和公钥固定

网络可达性

全面的单元和集成测试覆盖率

组件库

为了让Alamofire 专注于核心网络实现,Alamofire 软件基金会创建了额外的组件库,为 Alamofire 生态系统带来额外的功能。

AlamofireImage:一个图片库,包括图像响应序列化器、UIImage 和 UIImageView 的扩展、自定义图像滤镜、自动清除内存缓存和基于优先级的图像下载系统。

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 加载系统之上。系统的核心是URLSession 和 URLSessionTask 子类。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。

这实际上是AlamofireSession 类型上用于发出请求的两个顶层 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,同时允许组合来自各个组件(如method 和 headers )的请求,同时还允许每个传入 RequestInterceptors 和 Encodable 参数。

还有其他方法允许您使用Parameters 字典和 ParameterEncoding 类型来发出请求。不再推荐此 API,最终将被弃用并从 Alamofire 中删除。

这个API 的第二个版本要简单得多:

open func request(

    _ urlRequest: URLRequestConvertible,

    interceptor: RequestInterceptor? = nil

) -> DataRequest

此方法为遵循Alamofire 的 URLRequestConvertible 协议的任何类型创建 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 的类型:JSONParameterEncoder 和 URLEncodedFormParameterEncoder 。这些类型涵盖了现代服务使用的最常见的编码。

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 使用 URLEncodedFormEncoder 把 Encodable 类型编码为 URL 编码形式的 String。此编码器可用于自定义各种类型的编码,包括使用ArrayEncoding 的 Array、使用BoolEncoding 的Bool、使用DataEncoding 的 Data、使用DateEncoding 的 Date、使用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 编码为 1,false 编码为 0。这是默认情况。

.literal - 把 true 和 false 编码为字符串文本。

默认情况下,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) - 使用给定的 DateFormatter 对 Date 进行编码。

.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 的 JSONEncoder 对 Encodable 值进行编码,并将结果设置为 URLRequest 的 httpBody。如果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 = .convertToSnakeCaselet 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。

默认的AlamofireSession 为每个 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 的 DataRequest 和 DownloadRequest 都有相应的响应类型:DataResponse<Success, Failure: Error> 和 DownloadResponse<Success, Failure: Error>。这两个类型都由两个泛型组成:序列化类型和错误类型。默认情况下,所有响应值都将生成AFError 错误类型(DataResponse<Success, AFError>)。Alamofire 在其公共 API 中使用了更简单的AFDataResponse<Success> 和 AFDownloadResponse<Success>,它们总是有AFError 错误类型。UploadRequest 是 DataRequest 的一个子类,使用相同的 DataResponse 类型。

处理在Alamofire 中发出的DataRequest 或 UploadRequest 的 DataResponse 涉及到链接 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 - 未序列化的 Responsefunc response(

    queue: DispatchQueue = .main,

    completionHandler: @escaping (AFDataResponse<Data?>) -> Void

) -> Self

// Response Serializer Handler - Serialize using the passed Serializerfunc response<Serializer: DataResponseSerializerProtocol>(

    queue: DispatchQueue = .main,

    responseSerializer: Serializer,

    completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject>) -> Void

) -> Self

// Response Data Handler - Serialized into Datafunc responseData(

    queue: DispatchQueue = .main,

    completionHandler: @escaping (AFDataResponse<Data>) -> Void

) -> Self

// Response String Handler - Serialized into Stringfunc responseString(

    queue: DispatchQueue = .main,

    encoding: String.Encoding? = nil,

    completionHandler: @escaping (AFDataResponse<String>) -> Void

) -> Self

// Response JSON Handler - Serialized into Any Using JSONSerializationfunc responseJSON(

    queue: DispatchQueue = .main,

    options: JSONSerialization.ReadingOptions = .allowFragments,

    completionHandler: @escaping (AFDataResponse<Any>) -> Void

) -> Self

// Response Decodable Handler - Serialized into Decodable Typefunc responseDecodable<T: Decodable>(

    of type: T.Type = T.self,

    queue: DispatchQueue = .main,

    decoder: DataDecoder = JSONDecoder(),

    completionHandler: @escaping (AFDataResponse<T>) -> Void

) -> Self

没有一个响应handlers 对从服务器返回的HTTPURLResponse 执行任何验证。

例如,400..<500 和 500..<600 范围内的响应状态代码不会自动触发错误。Alamofire 使用响应验证链接来实现这一点。

响应Handler

响应handler 不计算任何响应数据。它只是直接从URLSessionDelegate 转发所有信息。它相当于使用 cURL 执行请求。

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

    debugPrint("Response: \(response)")

}

我们强烈建议您利用Response 和 Result 类型来利用其他响应序列化器。

响应Data Handler

responseData handler 使用 DataResponseSerializer 提取并验证服务器返回的数据。如果没有发生错误并且返回数据,则响应结果将为 .success,value 将为从服务器返回的 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 和 指定的 DataDecoder(Decoder 的协议抽象,可以从 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 实例,请查看高级用法

身份验证

身份验证使用系统自带的URLCredential 和 URLAuthenticationChallenge 处理。

这些身份验证APIs 用于提示授权的服务器,而不是一般用于需要身份验证或等效的 header APIs。

支持的身份验证方案

HTTP Basic

HTTP Digest

Kerberos

NTLM

HTTP Basic 身份验证

Request 的 authenticate 方法将在使用 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.download、DownloadRequest 和 DownloadResponse<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 都可以使用uploadProgress 和 downloadProgress 报告响应数据下载的上传进度和下载进度。

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 收集了 URLSessionTaskMetrics。URLSessionTaskMetrics 封装了一些关于底层网络连接、请求和响应时间的奇妙统计信息。

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"

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341