swift开发知识收集

IOS框架使用: Alamofire 5

2021-01-21  本文已影响0人  时光啊混蛋_97boy

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录


一、系统提供的原生框架URLSession的使用

1、请求网络的流程

a、请求网络的基本方式
let url = URL(string: "https://www.baidu.com")!
URLSession.shared.dataTask(with: url)
{ (data, response, error) in
    if error == nil
    {
        print("请求网络成功:\(String(describing: response))" )
    }
}.resume()

输出结果为:

请求网络成功:Optional(<NSHTTPURLResponse: 0x600001b1ee20> { URL: https://www.baidu.com/ } { Status Code: 200, Headers {
    "Content-Encoding" =     (
        gzip
    );
    "Content-Length" =     (
        1145
    );
    "Content-Type" =     (
        "text/html"
    );
    Date =     (
        "Thu, 21 Jan 2021 07:15:16 GMT"
    );
    Server =     (
        bfe
    );
} })

b、区别Configuration中的default与ephemeral
let defaultConfiguration = URLSessionConfiguration.default
let ephemeralConfiguration = URLSessionConfiguration.ephemeral
print("default 沙盒大小: \(String(describing: defaultConfiguration.urlCache?.diskCapacity))")
print("default 内存大小: \(String(describing: defaultConfiguration.urlCache?.memoryCapacity))")
print("ephemeral 沙盒大小: \(String(describing: ephemeralConfiguration.urlCache?.diskCapacity))")
print("ephemeral 内存大小: \(String(describing: ephemeralConfiguration.urlCache?.memoryCapacity))")

从输出结果中可以看到ephemeral的沙盒大小为0,而default有一个沙盒大小,即系统为default提供了一定大小的沙盒来存储证书等内容。

default 沙盒大小: Optional(10000000)
default 内存大小: Optional(512000)
ephemeral 沙盒大小: Optional(0)
ephemeral 内存大小: Optional(512000)

c、切换到后台停止下载问题
// 配置Configuration
let backgroundConfiguration = URLSessionConfiguration.background(withIdentifier: createID())

// 创建URLSession
let backgroundURLSession = URLSession.init(configuration: backgroundConfiguration, delegate: self, delegateQueue: OperationQueue.main)

// 开始下载
backgroundURLSession.downloadTask(with: downloadUrl).resume()
❶ 下载完成后进行沙盒迁移,拷贝下载完成的文件到用户目录(文件名以时间戳命名)
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)
{
    print("下载完成后文件位置:\(location)")
    let originalLocationPath = location.path
    let destinationPath = NSHomeDirectory() + "/Documents/" + currentDateTurnString() + ".mp4"
    print("文件移动后的位置:\(destinationPath)")
    let fileManager = FileManager.default
    try! fileManager.moveItem(atPath: originalLocationPath, toPath: destinationPath)
}

输出结果为:

下载完成后文件位置:file:///Users/xiejiapei/Library/Developer/CoreSimulator/Devices/9AF7A16E-76FF-4711-8BFA-66DDF38D03F2/data/Containers/Data/Application/67D3E5B6-989E-4137-9ED0-06F852F31CA3/Library/Caches/com.apple.nsurlsessiond/Downloads/com.xiejiapei.UseAlamofire/CFNetworkDownload_LX9nZT.tmp
文件移动后的位置:/Users/xiejiapei/Library/Developer/CoreSimulator/Devices/9AF7A16E-76FF-4711-8BFA-66DDF38D03F2/data/Containers/Data/Application/67D3E5B6-989E-4137-9ED0-06F852F31CA3/Documents/20210121145809.mp4
❷ 计算下载进度
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)
{
    print("bytesWritten: \(bytesWritten)\n totalBytesWritten: \(totalBytesWritten)\n totalBytesExpectedToWrite: \(totalBytesExpectedToWrite)")
    
    print("下载进度条:\( Double(totalBytesWritten) / Double(totalBytesExpectedToWrite) )")
}

输出结果为:

......
bytesWritten: 139246
 totalBytesWritten: 17218551
 totalBytesExpectedToWrite: 17244422
下载进度条:0.998499746758691
bytesWritten: 25871
 totalBytesWritten: 17244422
 totalBytesExpectedToWrite: 17244422
下载进度条:1.0
❸ 保存后台下载时的completionHandler,用于开启后台下载权限
var backgroundSessionCompletionHandler: (() -> Void)?

func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void)
{
    self.backgroundSessionCompletionHandler = completionHandler
}

如果不这样做,当用户将APP切换到后台的时候下载就会直接中断,但是当回到APP的时候会继续之前的进度进行下载。

2021-01-21 15:34:12.825608+0800 UseAlamofire[53235:1879599] BackgroundSession <01EC4B4A-A81E-4D5B-9004-CF615DEDFD87> connection to background transfer daemon interrupted
❹ 调用保存的后台下载回调,告诉系统及时更新屏幕
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession)
{
    print("让后台任务保持下载")
    DispatchQueue.main.async {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
        backgroundHandle()
    }
}

输出结果为:

让后台任务保持下载

需要注意的是,当我们切换到后台的时候,任务虽然还在下载,但是下载进度将不会再打印到控制台上,你不要以为下载终止了(😂,我起初就是这样以为的,还查了些资料)。


2、属性

常规属性
设置Cookie政策
设置安全策略
设置缓存策略
支持后台转移
支持自定义协议
支持多路径TCP
设置HTTP策略和代理属性
支持连接变化
默认缓存策略

3、HTTP

a、三次握手
为什么不是两次握手?

没有第三次握手,服务端就不知道客户端是否可以接收到消息,即确定服务端和客户端两者都可以发送消息也可以接收消息。

为什么不是四次握手?

通过三次握手已经确定服务端和客户端两者都可以发送消息也可以接收消息了,没必要再进行第四次握手。


b、四次挥手
为什么不是两次挥手?

因为服务器需要通过第三次挥手将还没有传输完成的数据全部传输完成。

为什么不是三次挥手?

因为服务器需要通过第三次挥手将还没有传输完成的数据全部传输完成,而客户端需要通过第四次挥手告诉服务端第三次挥手发送过来的数据已经全部接收完毕,通过四次挥手可以保证整个通讯过程的完整性。


c、使用Wireshark工具抓包
❶ 两个终端之间达到握手

当在下图左侧的终端输入的时候会自动显示到右侧终端进行同步,即两个终端之间成功达到了握手。

❷ 进入Loopback界面
❸ 在终端重新进行握手让Loopback开始监听
❹ 监听到三次握手流程中Seq和Ack的变化
❺ 在终端断掉握手让Loopback监听四次挥手
❺ 监听到四次挥手流程中Seq和Ack的变化

d、OSL七层协议
七层协议
包装与解包

二、初涉Alamofire

AlamofireHTTP 网络请求提供了一个优雅且可组合的接口。它没有实现自己的 HTTP 网络功能。取而代之的是,它建立在由Foundation 框架提供的URL 加载系统之上。系统的核心是 URLSessionURLSessionTask子类。

1、发起请求

Alamofire 为发出 HTTP 请求提供了多种方便的方法。

a、最简单的请求方式只需提供一个可以转换为 URL 的 String
AF.request("https://httpbin.org/get").response
{ response in
    debugPrint(response)
}

输出结果为:

[Request]: GET https://httpbin.org/get
    [Headers]: None
    [Body]: None
[Response]:
    [Status Code]: 200
    [Headers]:
        access-control-allow-credentials: true
        Access-Control-Allow-Origin: *
        Content-Length: 426
        Content-Type: application/json
        Date: Mon, 25 Jan 2021 05:35:54 GMT
        Server: gunicorn/19.9.0
    [Body]:
        {
          "args": {}, 
          "headers": {
            "Accept": "*/*", 
            "Accept-Encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8", 
            "Accept-Language": "en;q=1.0", 
            "Host": "httpbin.org", 
            "User-Agent": "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1", 
            "X-Amzn-Trace-Id": "Root=1-600e58ba-22848fce5ae105571e598f1e"
          }, 
          "origin": "222.76.251.163", 
          "url": "https://httpbin.org/get"
        }
[Network Duration]: 1.434872031211853s
[Serialization Duration]: 0.0s
[Result]: success(Optional(426 bytes))

这实际上是一种缩写形式,它的完整定义如下。此方法创建一个 DataRequest,允许传入多个参数。

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

b、为遵循 Alamofire 的 URLRequestConvertible 协议的任何类型创建 DataRequest
open func request(
    _ urlRequest: URLRequestConvertible,
    interceptor: RequestInterceptor? = nil
) -> DataRequest

2、HTTP Methods

a、作为 method 参数传递给 AF.request API

不同的 HTTP 方法可能有不同的语义,需要不同的参数编码,这取决于服务器的期望。例如,URLSessionAlamofire 不支持在 GET 请求中传递 body 数据,否则将返回错误。

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)

b、Alamofire 还提供了对 URLRequest 的扩展

以桥接将字符串返回到 HTTPMethod 值的 httpMethod 属性。

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

c、扩展HTTPMethod类型添加自定义值

如果需要使用 AlamofireHTTPMethod 类型不支持的 HTTP 方法,可以扩展该类型以添加自定义值。

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

3、请求参数和参数编码器

a、请求参数

Alamofire 支持将遵守 Encodable 协议的类型作为请求参数。这些请求参数通过遵循 ParameterEncoder 协议的参数编码器进行传递并添加到 URLRequest 中,最后通过网络发送。Alamofire 包含两种遵循 ParameterEncoder 协议的参数编码器:JSONParameterEncoderURLEncodedFormParameterEncoder 。这两种参数编码器涵盖了最常见的编码方式。

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

let login = Login(email: "2170928274@qq.com", password: "19970118")

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

输出结果为:

[Request]: POST https://httpbin.org/post
    [Headers]:
        Content-Type: application/json
    [Body]:
        {"email":"2170928274@qq.com","password":"19970118"}
[Response]:
    [Status Code]: 200
    [Headers]:
        access-control-allow-credentials: true
        Access-Control-Allow-Origin: *
        Content-Length: 682
        Content-Type: application/json
        Date: Mon, 25 Jan 2021 06:01:51 GMT
        Server: gunicorn/19.9.0
    [Body]:
        {
          "args": {}, 
          "data": "{\"email\":\"2170928274@qq.com\",\"password\":\"19970118\"}", 
          "files": {}, 
          "form": {}, 
          "headers": {
            "Accept": "*/*", 
            "Accept-Encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8", 
            "Accept-Language": "en;q=1.0", 
            "Content-Length": "51", 
            "Content-Type": "application/json", 
            "Host": "httpbin.org", 
            "User-Agent": "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1", 
            "X-Amzn-Trace-Id": "Root=1-600e5ecf-01e2ada305b5bd8a0e0e0dc6"
          }, 
          "json": {
            "email": "2170928274@qq.com", 
            "password": "19970118"
          }, 
          "origin": "222.76.251.163", 
          "url": "https://httpbin.org/post"
        }
[Network Duration]: 2.090991973876953s
[Serialization Duration]: 0.0s
[Result]: success(Optional(682 bytes))

b、使用 URL 编码参数的 GET 请求(默认编码方式)
// https://httpbin.org/get?foo=bar
let parameters = ["foo": "bar"]

// 下面三种方法都是等价的
AF.request("https://httpbin.org/get", parameters: parameters)
AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder.default)
AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder(destination: .methodDependent))

输出结果为:

[Request]: GET https://httpbin.org/get?foo=bar

c、使用 URL 编码参数的 POST 请求
// HTTP body: "qux[]=x&qux[]=y&qux[]=z&baz[]=a&baz[]=b&foo[]=bar"
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))

输出结果为:

[Request]: POST https://httpbin.org/post
    [Headers]:
        Content-Type: application/x-www-form-urlencoded; charset=utf-8
    [Body]: 73 bytes

"form": {
  "baz[]": [
    "a",
    "b"
  ],
  "foo[]": "bar",
  "qux[]": [
    "x",
    "y",
    "z"
  ]
}, 

d、JSON 编码参数的 POST 请求
// HTTP body: {"baz":["a","b"],"foo":["bar"],"qux":["x","y","z"]}
let parameters: [String: [String]] =
[
    "foo": ["bar"],
    "baz": ["a", "b"],
    "qux": ["x", "y", "z"]
]

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

输出结果为:

[Request]: POST https://httpbin.org/post
    [Headers]:
        Content-Type: application/json
    [Body]:
        {"foo":["bar"],"qux":["x","y","z"],"baz":["a","b"]}a

4、HTTP Headers

a、构造HTTP Headers

Alamofire 包含自己的 HTTPHeaders 类型,这是一种保持顺序且不区分大小写的 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 类型也可以采用如下方式进行构造:

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

b、Session 为每个 Request 提供一组默认的 headers

5、响应验证

默认情况下,无论响应的内容如何,Alamofire 都会将任何已完成的请求视为成功。如果响应具有不可接受的状态代码或 MIME 类型,则在响应处理程序之前调用 validate() 将导致生成错误。

a、自动验证

validate() 会自动验证状态代码是否在200..<300范围内,以及响应的Content-Type header 是否与请求的 Accept 匹配(如果有提供)。

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

输出结果为:

[Response]:
    [Status Code]: 200
    [Headers]:
        access-control-allow-credentials: true
        Access-Control-Allow-Origin: *
        Content-Length: 427
        Content-Type: application/json
        Date: Mon, 25 Jan 2021 07:38:36 GMT
        Server: gunicorn/19.9.0

b、手动验证
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)
        }
    }

输出结果为:

Validation Successful

6、响应处理

不管被序列化成哪种类型,结果都会通过闭包的参数response返回,如果是被序列化的数据,就通过resonse中的result.value来获取数据。源码中response闭包函数的返回值是Self,也就是Request,这就让我们能够使用链式访问来做一些很有意思的事情,任务按照顺序依次放入到队列中。

a、Handler

不计算任何响应数据。它只是直接从 URLSessionDelegate 转发所有信息。

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

// 序列化的 Response
func response<Serializer: DataResponseSerializerProtocol>(
    queue: DispatchQueue = .main,
    responseSerializer: Serializer,
    completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject>) -> Void
) -> Self

使用方式为:

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

输出结果为:

"Response: success(Optional(426 bytes))"

b、Data Handler

使用 DataResponseSerializer 提取并验证服务器返回的数据。如果没有发生错误并且返回数据,则响应结果将为 .successvalue 将为从服务器返回的 Data

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

使用方式为:

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

输出结果为:

"Response: success(427 bytes)"

c、String Handler

使用 StringResponseSerializer 将服务器返回的数据转换为具有指定编码的String。如果没有发生错误,并且服务器数据成功序列化为 String,则响应结果将为 .success,并且值的类型为 String

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

使用方式为:

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

输出结果为:

"Response: success(\"{\\n  \\\"args\\\": {}, \\n  \\\"headers\\\": {\\n    \\\"Accept\\\": \\\"*/*\\\", \\n    \\\"Accept-Encoding\\\": \\\"br;q=1.0, gzip;q=0.9, deflate;q=0.8\\\", \\n    \\\"Accept-Language\\\": \\\"en;q=1.0\\\", \\n    \\\"Host\\\": \\\"httpbin.org\\\", \\n    \\\"User-Agent\\\": \\\"UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1\\\", \\n    \\\"X-Amzn-Trace-Id\\\": \\\"Root=1-600e8ac6-70026c993647252b60805135\\\"\\n  }, \\n  \\\"origin\\\": \\\"218.104.139.115\\\", \\n  \\\"url\\\": \\\"https://httpbin.org/get\\\"\\n}\\n\")"

d、JSON Handler

使用指定的 JSONSerialization.ReadingOptions 将服务器返回的数据转换为 Any 类型。如果没有出现错误,并且服务器数据成功序列化为 JSON 对象,则响应 AFResult 将为 .success,值将为 Any 类型。

func responseJSON(
    queue: DispatchQueue = .main,
    options: JSONSerialization.ReadingOptions = .allowFragments,
    completionHandler: @escaping (AFDataResponse<Any>) -> Void
) -> Self

使用方式为:

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

输出结果为:

"Response: success({\n    args =     {\n    };\n    headers =     {\n        Accept = \"*/*\";\n        \"Accept-Encoding\" = \"br;q=1.0, gzip;q=0.9, deflate;q=0.8\";\n        \"Accept-Language\" = \"en;q=1.0\";\n        Host = \"httpbin.org\";\n        \"User-Agent\" = \"UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1\";\n        \"X-Amzn-Trace-Id\" = \"Root=1-600e8b4a-28fe0a1064c7a6e1033414fa\";\n    };\n    origin = \"218.104.139.115\";\n    url = \"https://httpbin.org/get\";\n})"

e、可解码类型的Handler

使用 DecodableResponseSerializer 和 指定的 DataDecoderDecoder 的协议抽象,可以从 Data 解码)将服务器返回的数据转换为传递进来的 Decodable 类型。如果没有发生错误,并且服务器数据已成功解码为 Decodable 类型,则响应 Result 将为 .success,并且 value 将为传递进来的类型。

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

使用方式为:

struct HTTPBinResponse: Decodable
{
    let url: String
}

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

输出结果为:

"Response: success(UseAlamofire.HTTPBinResponse(url: \"https://httpbin.org/get\"))"

f、链式响应

没有一个响应 handlers 对从服务器返回的 HTTPURLResponse 执行任何验证。例如,400..<500500..<600 范围内的响应状态代码不会自动触发错误。Alamofire 使用链式的响应验证来实现这一点。对同一请求使用多个响应 handlers 需要多次序列化服务器数据,每个响应 handlers 均处理一次。通常应避免对同一请求使用多个响应 handlers,特别是在生产环境中。

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

输出结果为:

Response String: Optional("{\n  \"args\": {}, \n  \"headers\": {\n    \"Accept\": \"*/*\", \n    \"Accept-Encoding\": \"br;q=1.0, gzip;q=0.9, deflate;q=0.8\", \n    \"Accept-Language\": \"en;q=1.0\", \n    \"Host\": \"httpbin.org\", \n    \"User-Agent\": \"UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1\", \n    \"X-Amzn-Trace-Id\": \"Root=1-600e8e25-66ed7b9b0986f02971550391\"\n  }, \n  \"origin\": \"218.104.139.115\", \n  \"url\": \"https://httpbin.org/get\"\n}\n")
Response JSON: Optional({
    args =     {
    };
    headers =     {
        Accept = "*/*";
        "Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
        "Accept-Language" = "en;q=1.0";
        Host = "httpbin.org";
        "User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
        "X-Amzn-Trace-Id" = "Root=1-600e8e25-66ed7b9b0986f02971550391";
    };
    origin = "218.104.139.115";
    url = "https://httpbin.org/get";
})

g、响应队列

默认情况下,传递给响应 handler 的闭包在 .main 队列上执行,但可以传递一个指定的 DispatchQueue 来执行闭包。实际的序列化工作(将 Data 转换为其他类型)总是在后台队列上执行。

let utilityQueue = DispatchQueue.global(qos: .utility)
AF.request("https://httpbin.org/get").responseJSON(queue: utilityQueue)
{ response in
    print("在全局队列上执行此网络请求:\(Thread.current)")
    debugPrint(response)
}

输出结果为:

在全局队列上执行此网络请求:<NSThread: 0x600000401a00>{number = 8, name = (null)}

7、身份验证

a、自动提供 URLCredential

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

输出结果为:

[Response]:
    [Status Code]: 200
    [Headers]:
        access-control-allow-credentials: true
        Access-Control-Allow-Origin: *
        Content-Length: 47
        Content-Type: application/json
        Date: Mon, 25 Jan 2021 10:01:37 GMT
        Server: gunicorn/19.9.0
    [Body]:
        {
          "authenticated": true, 
          "user": "user"
        }

b、自己提供 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)
    }

输出结果同上。


8、下载文件

a、下载数据到文件中

除了将数据提取到内存中之外,Alamofire 还提供了 Session.downloadDownloadRequestDownloadResponse<Success,Failure:Error> 以方便下载数据到磁盘。虽然下载到内存中对小负载(如大多数 JSON API 响应)非常有用,但获取更大的资源(如图像和视频)应下载到磁盘,以避免应用程序出现内存问题。DownloadRequest 具有与 DataRequest 相同的大多数响应 handlers。但是,由于它将数据下载到磁盘,因此序列化响应涉及从磁盘读取,还可能涉及将大量数据读入内存。在设计下载处理时,记住这些事实是很重要的。

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

b、下载文件的存放位置

所有下载的数据最初都存储在系统临时目录中。它最终会在将来的某个时候被系统删除,所以如果它需要更长的寿命,将文件移到其他地方是很重要的。我们可以提供 Destination 闭包,将文件从临时目录移动到最终的存放位置。在临时文件实际移动到 destinationURL 之前,将执行闭包中指定的 Options。当前支持的两个 Options 是:.createIntermediateDirectories 如果指定,则为目标 URL 创建中间目录。.removePreviousFile如果指定,则从目标 URL中删除以前的文件。

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)
        self.imageView.image = image!
    }
}

输出结果为:

[Request]: GET https://httpbin.org/image/png
    [Headers]: None
    [Body]: None
[Response]:
    [Status Code]: 200
    [Headers]:
        access-control-allow-credentials: true
        Access-Control-Allow-Origin: *
        Content-Length: 8090
        Content-Type: image/png
        Date: Mon, 25 Jan 2021 10:14:50 GMT
        Server: gunicorn/19.9.0
[File URL]: /Users/xiejiapei/Library/Developer/CoreSimulator/Devices/9AF7A16E-76FF-4711-8BFA-66DDF38D03F2/data/Containers/Data/Application/730436F5-DE88-42CA-96AF-B5FC5A4C9019/Documents/image.png
[Resume Data]: None
[Network Duration]: 1.8333059549331665s
[Serialization Duration]: 0.0s
[Result]: success(Optional(file:///Users/xiejiapei/Library/Developer/CoreSimulator/Devices/9AF7A16E-76FF-4711-8BFA-66DDF38D03F2/data/Containers/Data/Application/730436F5-DE88-42CA-96AF-B5FC5A4C9019/Documents/image.png))

文件存储的位置为

还可以使用建议的文件存储位置,效果同上。

let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
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)
        self.imageView.image = image!
    }
}

c、下载进度

任何 DownloadRequest 都可以使用 downloadProgress 报告下载进度。只有在服务器正确返回可用于计算进度的 Content-Length header 时才能工作。如果没有这个 header,进度将保持在 0.0,直到下载完成,此时进度将跳到 1.0。还可以接收一个queue参数,该参数定义应该对哪个 DispatchQueue 调用下载进度闭包。

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

AF.download("https://httpbin.org/image/png")
    .downloadProgress(queue: utilityQueue)
    { progress in
        print("下载进度: \(progress.fractionCompleted)")
    }
    .responseData
    { response in
        if let data = response.value
        {
            let image = UIImage(data: data)
            self.imageView.image = image!
        }
    }

输出结果为:

下载进度: 1.0

d、取消和恢复下载

除了所有请求类都有 cancel() 方法外,DownloadRequest 还可以生成恢复数据,这些数据可以用于以后恢复下载。此 API 有两种形式:1)cancel(producingResumeData: Bool),它允许控制是否生成恢复数据,但仅在 DownloadResponse 可用;2)cancel(byProducingResumeData: (_ resumeData: Data?) -> Void),它执行相同的操作,但恢复数据在 completion handler中可用。如果DownloadRequest 被取消或中断,则底层的 URLSessionDownloadTask 可能会生成恢复数据。如果发生这种情况,可以重新使用恢复数据来重新启动停止的 DownloadRequest

let download = AF.download("https://httpbin.org/image/png")

var resumeData: Data!

// 正常下载
download.responseData
{ response in
    if let data = response.value
    {
        let image = UIImage(data: data)
        self.imageView.image = image!
    }
}

// 从cancel的回调闭包中获得resumeData
download.cancel
{ data in
    resumeData = data
}

// 使用resumeData继续下载
AF.download(resumingWith: resumeData).responseData
{ response in
    if let data = response.value
    {
        let image = UIImage(data: data)
        self.imageView.image = image!
    }
}

9、上传数据到服务器

当使用 JSONURL 编码的参数向服务器发送相对少量的数据时,request() 通常就足够了。如果需要从内存、文件 URLInputStream 中的 Data 发送大量数据,那么 upload() 就是您想要使用的。

a、上传 Data
let data = Data("XieJiaPei".utf8)

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

输出结果为:

[Result]: success({
    args =     {
    };
    data = "";
    files =     {
    };
    form =     {
        XieJiaPei = "";
    };
    headers =     {
        Accept = "*/*";
        "Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
        "Accept-Language" = "en;q=1.0";
        "Content-Length" = 9;
        "Content-Type" = "application/x-www-form-urlencoded";
        Host = "httpbin.org";
        "User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
        "X-Amzn-Trace-Id" = "Root=1-600f6966-48de0e395d96f17f396e2b67";
    };
    json = "<null>";
    origin = "222.76.251.163";
    url = "https://httpbin.org/post";
})
b、上传多表单数据
AF.upload(multipartFormData: { multipartFormData in
    multipartFormData.append(Data("Boy".utf8), withName: "JiaPei")
    multipartFormData.append(Data("Girl".utf8), withName: "YuQing")
}, to: "https://httpbin.org/post")
.responseJSON { response in
    debugPrint(response)
}

输出结果为:

[Result]: success({
    args =     {
    };
    data = "";
    files =     {
    };
    form =     {
        JiaPei = Boy;
        YuQing = Girl;
    };
    headers =     {
        Accept = "*/*";
        "Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
        "Accept-Language" = "en;q=1.0";
        "Content-Length" = 228;
        "Content-Type" = "multipart/form-data; boundary=alamofire.boundary.6d21176fdb63050f";
        Host = "httpbin.org";
        "User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
        "X-Amzn-Trace-Id" = "Root=1-600f6d84-25a167ab5ddb02c206ff4697";
    };
    json = "<null>";
    origin = "218.104.139.115";
    url = "https://httpbin.org/post";
})
c、上传文件
let fileURL = Bundle.main.url(forResource: "girl", withExtension: "mp4")!
AF.upload(fileURL, to: "https://httpbin.org/post").responseJSON
{ response in
    debugPrint(response)
}

输出结果为:

d、上传进度

当用户等待上传完成时,有时向用户显示上传的进度会很方便。任何 UploadRequest 都可以使用 uploadProgressdownloadProgress 报告响应数据下载的上传进度和下载进度。

let fileURL = Bundle.main.url(forResource: "girl", withExtension: "mp4")!

if FileManager.default.fileExists(atPath: fileURL.path)
{
    AF.upload(fileURL, to: "https://httpbin.org/post")
        .uploadProgress
        { progress in
            print("上传进度: \(progress.fractionCompleted)")
        }
        .responseJSON
        { response in
            print("上传完成")
            print(response)
        }
}
else
{
    print("没有找到文件")
}

输出结果为:

上传进度: 0.01685915418681258
上传进度: 0.5394929339780026
上传进度: 0.6743661674725031
上传进度: 0.9441126344615044
上传进度: 1.0

10、网络可达性

监听移动网络和 WiFi 网络接口的主机和地址的可达性变化。

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

manager?.startListening
{ status in
    print("网络状态发生改变: \(status)")
}

输出结果为:

网络状态发生改变: reachable(Alamofire.NetworkReachabilityManager.NetworkReachabilityStatus.ConnectionType.ethernetOrWiFi)

三、玩转Alamofire

1、Session

a、Session.default

AlamofireSession 在职责上大致等同于它维护的 URLSession 实例:它提供 API 来生成各种 Request 子类,这些子类封装了不同的 URLSessionTask 子类,以及封装应用于实例生成的所有 Request 的各种配置。Session 提供了一个 default 单例实例,并且 AF 实际上就是 Session.default。因此,以下两个语句是等效的:

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

let session = Session.default
session.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}

b、创建自定义的 Session 实例

使用以下便利初始化器,并将结果存储在整个应用程序使用的单个实例中。此初始化器允许自定义 Session 的所有行为。

let session = Session.init(...)

public convenience init
(
    configuration: URLSessionConfiguration = URLSessionConfiguration.af.default,
    delegate: SessionDelegate = SessionDelegate(),
    rootQueue: DispatchQueue = DispatchQueue(label: "org.alamofire.session.rootQueue"),
    startRequestsImmediately: Bool = true,
    requestQueue: DispatchQueue? = nil,
    serializationQueue: DispatchQueue? = nil,
    interceptor: RequestInterceptor? = nil,
    serverTrustManager: ServerTrustManager? = nil,
    redirectHandler: RedirectHandler? = nil,
    cachedResponseHandler: CachedResponseHandler? = nil,
    eventMonitors: [EventMonitor] = []
)

c、使用 URLSessionConfiguration 创建 Session

要自定义底层 URLSession 的行为,可以提供自定义的 URLSessionConfiguration 实例。建议从改变URLSessionConfiguration.af.default 实例开始,因为它添加了 Alamofire 提供的默认 Accept-EncodingAccept-LanguageUser-Agent headers

let configuration = URLSessionConfiguration.af.default
configuration.allowsCellularAccess = false

let customConfigurationSession = Session(configuration: configuration)
customConfigurationSession.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}

d、SessionDelegate

SessionDelegate 实例封装了对各种 URLSessionDelegate 和相关协议回调的所有处理。默认情况下,Session 将在添加至少一个响应 handler 后立即对 Request 调用 resume()。将 startRequestsImmediately 设置为 false 需要手动调用所有请求的 resume() 方法。

let session = Session(startRequestsImmediately: false)
session.request("https://httpbin.org/get").resume().responseJSON
{ response in
    debugPrint(response)
}

输出结果为:

[Request]: GET https://httpbin.org/get
    [Headers]: None
    [Body]: None
[Response]: None
[Network Duration]: None
[Serialization Duration]: 0.00031406700145453215s
[Result]: failure(Alamofire.AFError.sessionDeinitialized)

e、Session 的 DispatchQueue

默认情况下,Session 实例对所有异步工作使用单个 DispatchQueue。这包括 URLSessiondelegate OperationQueueunderlyingQueue,用于所有 URLRequest 创建、所有响应序列化工作以及所有内部 SessionRequest 状态的改变。如果性能分析显示瓶颈在于 URLRequest 的创建或响应序列化,则可以为 Session 的每个工作区域提供单独的 DispatchQueue。提供的任何自定义 rootQueue 都必须是串行队列,但 requestQueueserializationQueue 可以是串行或并行队列。通常建议使用串行队列,除非性能分析显示工作被延迟,在这种情况下,使队列并行可能有助于提高整体性能。

let rootQueue = DispatchQueue(label: "com.app.session.rootQueue")
let requestQueue = DispatchQueue(label: "com.app.session.requestQueue")
let serializationQueue = DispatchQueue(label: "com.app.session.serializationQueue")

let session = Session(
    rootQueue: rootQueue,
    requestQueue: requestQueue,
    serializationQueue: serializationQueue
)

f、添加其他信息
❶ 添加 RequestInterceptor

AlamofireRequestInterceptor 协议(RequestAdapter & RequestRetrier)提供了重要而强大的请求自适应和重试功能。

let policy = RetryPolicy()
let session = Session(interceptor: policy)
❷ 添加 ServerTrustManager

AlamofireServerTrustManager 类封装了域名和遵循 ServerTrustEvaluating 协议的类型实例之间的映射,这提供了定制 Session 处理 TLS 安全性的能力。这包括使用证书和公钥固定以及证书吊销检查。

let manager = ServerTrustManager(evaluators: ["httpbin.org": PinnedCertificatesTrustEvaluator()])
let managerSession = Session(serverTrustManager: manager)
managerSession.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}
❸ 添加 RedirectHandler

AlamofireRedirectHandler 协议定制了 HTTP 重定向响应的处理。

let redirector = Redirector(behavior: .follow)
let redirectorSession = Session(redirectHandler: redirector)
redirectorSession.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}
❹ 添加 CachedResponseHandler

AlamofireCachedResponseHandler 协议定制了响应的缓存,可以在 SessionRequest 层级使用。

let cacher = ResponseCacher(behavior: .cache)
let cacherSession = Session(cachedResponseHandler: cacher)
cacherSession.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}
❺ 添加 EventMonitor

AlamofireEventMonitor 协议提供了对 Alamofire 内部事件的强大洞察力。它可以用来提供日志和其他基于事件的特性。

let monitor = ClosureEventMonitor()
monitor.requestDidCompleteTaskWithError =
{ (request, task, error) in
    debugPrint(request)
}
let monitorSession = Session(eventMonitors: [monitor])
monitorSession.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}

g、从 URLSession 创建实例

除了前面提到的便利初始化器之外,还可以直接从 URLSession 初始化 Session。但是,在使用这个初始化器时需要记住几个要求,因此建议使用便利初始化器。其中包括:

let rootQueue = DispatchQueue(label: "org.alamofire.customQueue")
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.underlyingQueue = rootQueue
let delegate = SessionDelegate()
let configuration = URLSessionConfiguration.af.default
let urlSession = URLSession(configuration: configuration,
                            delegate: delegate,
                            delegateQueue: queue)
let session = Session(session: urlSession, delegate: delegate, rootQueue: rootQueue)

2、Request

a、创建请求
URLConvertible协议

可以使用遵循 URLConvertible 协议的类型来构造 URL,然后使用 URL 在内部构造 URL 请求。默认情况下,StringURLURLComponents遵循了URLConvertible协议,允许将它们中的任何一个作为 URL 参数传递给 requestuploaddownload方法。

let urlString = "https://httpbin.org/get"
AF.request(urlString)

let url = URL(string: urlString)!
AF.request(url)

let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)!
AF.request(urlComponents)
URLRequestConvertible协议

遵循 URLRequestConvertible 协议的类型可用于构造 URLRequest。默认情况下,URLRequest 遵循 URLRequestConvertible,允许将其直接传递到 requestuploaddownload 方法中。

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

let parameters = ["foo": "bar"]

do
{
    urlRequest.httpBody = try JSONEncoder().encode(parameters)
}
catch
{
    // Handle error.
    print("出错了")
}

urlRequest.headers.add(.contentType("application/json"))

AF.request(urlRequest)
    .responseJSON
    { response in
        debugPrint(response)
    }

输出结果为:

[Result]: success({
    args =     {
    };
    data = "{\"foo\":\"bar\"}";
    files =     {
    };
    form =     {
    };
    headers =     {
        Accept = "*/*";
        "Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
        "Accept-Language" = "en;q=1.0";
        "Content-Length" = 13;
        "Content-Type" = "application/json";
        Host = "httpbin.org";
        "User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
        "X-Amzn-Trace-Id" = "Root=1-600fbced-3ebfeeb20ee086fa017998b4";
    };
    json =     {
        foo = bar;
    };
    origin = "218.104.139.115";
    url = "https://httpbin.org/post";
})

b、请求管道

一旦使用 Request 子类的初始参数或 URLRequestConvertible 创建了它,它就会通过组成 Alamofire 请求管道的一系列步骤进行传递。

请求步骤
  1. 初始参数(如 HTTP 方法、headers 和参数)被封装到内部 URLRequestConvertible 值中。
  2. URLRequestConvertible 值调用 asURLRequest(),创建第一个 URLRequest 值。此值将传递给 Request 并存储在 requests 中。
  3. 如果 SessionRequestAdapterRequestInterceptor,则使用先前创建的 URLRequest 调用它们。然后将调整后的 URLRequest 传递给 Request 并存储在 requests 中。
  4. Session 调用 Request 创建的 URLSessionTask,以基于 URLRequest 执行网络请求。
  5. 完成 URLSessionTask 并收集 URLSessionTaskMetrics(日志)后,Request 将执行其 Validator
  6. 请求执行已附加的任何响应 handlers,如 responseDecodable
触发重试

在这些步骤中的任何一个,都可以通过创建或接收的 Error 值来表示失败,然后将错误值传递给关联的 Request。例如,除了步骤 1 和 4 之外,上面的所有其他步骤都可以创建一个Error,然后传递给响应 handlers 或可供重试。一旦将错误传递给 RequestRequest 将尝试运行与 SessionRequest 关联的任何 RequestRetrier。如果任何 RequestRetrier 选择重试该 Request,则将再次运行完整的管道。RequestRetrier也会产生 Error,但这些错误不会触发重试。

失败原因

c、请求种类

Alamofire 执行的每个请求都由特定的类、DataRequestUploadRequestDownloadRequest 封装。这些类中的每一个都封装了每种类型请求所特有的功能,但是 DataRequestDownloadRequest 继承自一个公共的父类 RequestUploadRequest 继承自 DataRequest)。Request 实例从不直接创建,而是通过各种 request方法之一从会话 Session 中自动生成。

DataRequest

DataRequestRequest 的一个子类,它封装了 URLSessionDataTask,将服务器响应下载到存储在内存中的 Data 中。因此,必须认识到,超大下载量可能会对系统性能产生不利影响。对于这些类型的下载,建议使用 DownloadRequest 将数据保存到磁盘。

除了 Request 提供的属性之外,DataRequest 还有一些属性。其中包括 data(这是服务器响应的累积 Data)和 convertible(这是创建 DataRequest 时使用的 URLRequestConvertible,其中包含创建实例的原始参数)。

默认情况下,DataRequest 不验证响应。相反,必须向其中添加对 validate() 的调用,以验证各种属性是否有效。添加 validate() 确保响应状态代码在 200..<300 范围内,并且响应的 Content-Type 与请求的 Accept 匹配。通过传递 Validation 闭包可以进一步定制验证。

UploadRequest

UploadRequestDataRequest 的一个子类,它封装 URLSessionUploadTask、将 Data、磁盘上的文件或 InputStream 上传到远程服务器。

除了 DataRequest 提供的属性外,UploadRequest 还有一些属性。其中包括一个 FileManager 实例,用于在上传文件时自定义对磁盘的访问,以及 uploadupload 封装了用于描述请求的 URLRequestConvertible 值和确定要执行的上传类型的Uploadable 值。

DownloadRequest

DownloadRequestRequest 的一个具体子类,它封装了 URLSessionDownloadTask,将响应数据下载到磁盘。DownloadRequest 除了由 Request 提供的属性外,还有一些属性。其中包括取消 DownloadRequest 时生成的数据 resumeData(可用于以后继续下载)和 fileURL(下载完成后下载文件对应的 URL)。

除了支持 Request 提供的 cancel() 方法外,DownloadRequest 还包括了其他两种取消方法。

// 可以选择在取消时设置 resumeData 属性
cancel(producingResumeData shouldProduceResumeData: Bool)

// 将生成的恢复数据提供给传递进来的闭包
cancel(byProducingResumeData completionHandler: @escaping (_ data: Data?) -> Void)

d、请求状态

尽管 Request 不封装任何特定类型的请求,但它包含 Alamofire 执行的所有请求所共有的状态和功能,表示 Request 生命周期中的主要事件。请求在创建后以 .initialized 状态启动。通过调用适当的生命周期方法,可以挂起、恢复和取消 Request

public enum State 
{
    case initialized
    case resumed
    case suspended
    case cancelled
    case finished
}

e、请求进度

为了跟踪请求的进度,Request 提供了 uploadProgressdownloadProgress 属性以及基于闭包的 uploadProgressdownloadProgress 方法。与所有基于闭包的 Request APIs 一样,进度 APIs 可以与其他方法链接到 Request 之后,但是应该在添加任何响应 handlers(如 responseJSON)之前添加到请求中。但并不是所有的 Request 子类都能够准确地报告它们的进度,有些可能有其他依赖项来报告它们的进度。对于下载进度,只有一个要求,服务器响应必须包含 Content-Length header

AF.request("https://httpbin.org/get")
    .uploadProgress
    { progress in
        print("上传进度: \(progress.fractionCompleted)")
    }
    .downloadProgress
    { progress in
        print("下载进度: \(progress.fractionCompleted)")
    }
    .responseJSON
    { response in
        debugPrint(response)
    }

输出结果为:

下载进度: 1.0

[Result]: success({
    args =     {
    };
    headers =     {
        Accept = "*/*";
        "Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
        "Accept-Language" = "en;q=1.0";
        Host = "httpbin.org";
        "User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
        "X-Amzn-Trace-Id" = "Root=1-600f8c8f-1550b7515a19e35b7f534f0e";
    };
    origin = "218.104.139.115";
    url = "https://httpbin.org/get";
})
对于上传进度,可以通过以下方式确定进度

f、调整和重试请求
重定向

AlamofireRedirectHandler 协议提供了对 Request 的重定向处理的控制和定制。除了每个 Session RedirectHandler 之外,每个 Request 都可以被赋予属于自己的 RedirectHandler,并且这个 handler 将重写 Session 提供的任何 RedirectHandler

let redirector = Redirector(behavior: .follow)
AF.request("https://httpbin.org/get")
    .redirect(using: redirector)
    .responseDecodable(of: String.self)
    { response in
        debugPrint(response)
    }
重试

AlamofireRequestInterceptor 协议由 RequestAdapterRequestRetrier 协议组成。在身份验证系统中,向每个 Request 添加一个常用的 headers,并在授权过期时重试 RequestAlamofire 还包含一个内置的 RetryPolicy 类型,当由于各种常见的网络错误而导致请求失败时,可以轻松重试。

RequestAdapter 协议允许在通过网络发出之前检查和修改 Session 执行的每个 URLRequest。适配器的一个常见的用途,是在身份验证后面为请求添加 Authorization headerRequestRetrier 协议允许重试在执行时遇到错误的请求。


3、Security

在与服务器和 web 服务通信时使用安全的 HTTPS 连接是保护敏感数据的重要步骤。默认情况下,Alamofire 接收与 URLSession 相同的自动 TLS 证书和证书链验证。虽然这保证了证书链的有效性,但并不能防止中间人(MITM)攻击或其他潜在的漏洞。为了减轻 MITM 攻击,处理敏感客户数据或财务信息的应用程序应使用 AlamofireServerTrustEvaluating 协议提供的证书或公钥固定。

a、评估服务器信任
ServerTrustEvaluting 协议
// 此方法提供从底层 URLSession 接收的 SecTrust 值和主机 String,并提供执行各种评估的机会
func evaluate(_ trust: SecTrust, forHost host: String) throws
ServerTrustManager
let evaluators: [String: ServerTrustEvaluating] = 
[
    // 默认情况下,包含在 app bundle 的证书会自动固定。
    "cert.example.com": PinnedCertificatesTrustEvalutor(),
    // 默认情况下,包含在 app bundle 的来自证书的公钥会被自动使用。
    "keys.example.com": PublicKeysTrustEvalutor(),
]

let manager = ServerTrustManager(evaluators: serverTrustPolicies)

b、Logging

EventMonitor 协议的最大用途是实现相关事件的日志记录。

final class Logger: EventMonitor
{
    let queue = DispatchQueue(label: "xiejiapei")

    // Event called when any type of Request is resumed.
    func requestDidResume(_ request: Request)
    {
        print("Resuming: \(request)")
    }

    // Event called whenever a DataRequest has parsed a response.
    func request<Value>(_ request: DataRequest, didParseResponse response: DataResponse<Value, AFError>)
    {
        debugPrint("Finished: \(response)")
    }
}

let logger = Logger()
let session = Session(eventMonitors: [logger])
session.request("https://httpbin.org/get").responseJSON
{ response in
    debugPrint(response)
}

输出结果为:

[Request]: GET https://httpbin.org/get
    [Headers]: None
    [Body]: None
[Response]: None
[Network Duration]: None
[Serialization Duration]: 4.879705375060439e-05s
[Result]: failure(Alamofire.AFError.sessionDeinitialized)
Resuming: GET https://httpbin.org/get
"Finished: failure(Alamofire.AFError.sessionDeinitialized)"

Demo

Demo在我的Github上,欢迎下载。
UseFrameworkDemo

参考文献

上一篇下一篇

猜你喜欢

热点阅读