Alamofire 4.0 迁移指南

 

Alamofire 4.0 是 Alamofire 最新的一个大版本更新, 一个基于 Swift 的 iOS, tvOS, macOS, watchOS 的 HTTP 网络库. 做为一个大版本更新, 就像语义上那样, 4.0 的 API 引入了一些破坏性修改.html

这篇导引旨在帮助你们从 Alamofire 3.x 平滑过渡到最新版本, 同时也解释一下新的设计和结构, 以及功能上的更新.git

要求

  • iOS 8.0+, macOS 10.10.0+, tvOS 9.0+ 以及 watchOS 2.0+
  • Xcode 8.1+
  • Swift 3.0+

那些想要在 iOS 8 或者 macOS 10.9 使用 Alamofire 的, 请使用 3.x 版本的最新 release(同时支持 Swift 2.2以及2.3)github

升级的好处

  • 完美适配 Swift 3: 跟进了新的 API 设计规范.
  • 新的错误处理系统: 根据提案 SE-0112 里的新模式, 新增了 AFError 类型.
  • 新的 RequestAdapter 协议: 能够在初始化 Request 的时候进行快速便捷的适配, 例如在请求头里加入Authorization
  • 新的 RequestRetrier 协议: 能够检测而且重试失败的 Request, 甚至能够本身根据一系列需求去构建一套验证的解决方案( OAuth1, OAuth2, xAuth, Basic Auth 之类的).
  • 新的 Parameter Encoding 协议: 取代掉以前的 ParameterEncoding 枚举, 容许你更简单的拓展和自定义, 而且在错误时抛出异常, 而不是简单的返回一个元组.
  • 新的请求类型: 包括 DataRequestDownloadRequestUploadRequest 和 StreamRequest, 实现了特定的进度, 验证和序列化的 API 以及各自的 Request 类型.
  • 新的进度 API: 包括 downloadProgress 和 uploadProgress, 支持 progress 和 Int64 类型, 而且会在指定的线程运行, 默认为主线程.
  • 更强大的数据验证: 在验证失败的时候, 包括 data 或者 temporaryURL 和 destinationURL 均可以使用内联的闭包去转化服务器返回的错误信息
  • 新的下载地址处理: 你能够得到完整的控制权, 而不是像以前那样只是提供一个 destinationURL, 还得建立临时文件夹, 删掉以前的文件.
  • 新的 Response 类型: 统一 response 的 API, 而且为全部下载任务提供 temporaryURL 和 downloadURL, 以及其它新平台上的任务属性.

API 破坏性的修改

Alamofire 4 跟进了 Swift 3 里全部的修改, 包括 API 设计规范. 所以, 几乎全部 Alamofire 的 API 都进行了必定程度的修改. 咱们没办法把这些修改所有在文档里列出来, 因此咱们会把最经常使用的那些 API 列出来, 而后告诉你们这些 API 进行了哪些修改, 而不是期望那些有时帮倒忙的编译错误提示.swift

命名空间的修改

一些经常使用的类移到了全局命名空间成为一级类, 让他们更容易使用.api

  • Manager 改成 SessionManager
  • Request.TaskDelegate 改成 TaskDelegate
  • Request.DataTaskDelegate 改成 DataTaskDelegate
  • Request.DownloadTaskDelegate 改成 DownloadTaskDelegate
  • Request.UploadTaskDelegate 改成 UploadTaskDelegate

咱们也从新调整了文件结构和组织模式, 帮助更好的跟进代码. 咱们但愿这可让更多用户去了解内部结构和 Alamofire 的具体实现. 只是就是力量.安全

生成请求

生成请求是 Alamofire 里最主要的操做, 这里有 3.x 以及 4 的等效代码对比.服务器

Data Request - Simple with URL string

// Alamofire 3
Alamofire.request(.GET, urlString).response { request, response, data, error in
print(request)
print(response)
print(data)
print(error)
}
 
// Alamofire 4
Alamofire.request(urlString).response { response in // 默认为 `.get` 方法
debugPrint(response)
}

Data Request - Complex with URL string

// Alamofire 3
let parameters: [String: AnyObject] = ["foo": "bar"]
 
Alamofire.request(.GET, urlString, parameters: parameters, encoding: .JSON)
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
print("Bytes: \(bytesRead), Total Bytes: \(totalBytesRead), Total Bytes Expected: \(totalBytesExpectedToRead)")
}
.validate { request, response in
// 自定义的校验闭包 (访问不到服务器返回的数据)
return .success
}
.responseJSON { response in
debugPrint(response)
}
 
// Alamofire 4
let parameters: Parameters = ["foo": "bar"]
 
Alamofire.request(urlString, method: .get, parameters: parameters, encoding: JSONEncoding.default)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
print("进度: \(progress.fractionCompleted)")
}
.validate { request, response, data in
// 自定义的校验闭包, 如今加上了 `data` 参数(容许你提早转换数据以便在必要时挖掘到错误信息)
return .success
}
.responseJSON { response in
debugPrint(response)
}

Download Request - Simple With URL string

// Alamofire 3
let destination = DownloadRequest.suggestedDownloadDestination()
 
Alamofire.download(.GET, urlString, destination: destination).response { request, response, data, error in
// fileURL 在哪, 怎么获取?
print(request)
print(response)
print(data)
print(error)
}
 
// Alamofire 4
let destination = DownloadRequest.suggestedDownloadDestination()
 
Alamofire.download(urlString, to: destination).response { response in // 默认为 `.get` 方法
print(response.request)
print(response.response)
print(response.temporaryURL)
print(response.destinationURL)
print(response.error)
}

Download Request - Simple With URLRequest

// Alamofire 3
let destination = DownloadRequest.suggestedDownloadDestination()
 
Alamofire.download(urlRequest, destination: destination).validate().responseData { response in
// fileURL 在哪里, 太难获取了
debugPrint(response)
}
 
// Alamofire 4
Alamofire.download(urlRequest, to: destination).validate().responseData { response in
debugPrint(response)
print(response.temporaryURL)
print(response.destinationURL)
}

Download Request - Complex With URL String

// Alamofire 3
let fileURL: NSURL
let destination: Request.DownloadFileDestination = { _, _ in fileURL }
let parameters: [String: AnyObject] = ["foo": "bar"]
 
Alamofire.download(.GET, urlString, parameters: parameters, encoding: .JSON, to: destination)
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
print("Bytes: \(bytesRead), Total Bytes: \(totalBytesRead), Total Bytes Expected: \(totalBytesExpectedToRead)")
}
.validate { request, response in
// 自定义的校验实现(获取不到临时下载位置和目标下载位置)
return .success
}
.responseJSON { response in
print(fileURL) // 只有在闭包捕获了的状况才能获取到, 不够理想
debugPrint(response)
}
 
// Alamofire 4
let fileURL: URL
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
return (fileURL, [.createIntermediateDirectories, .removePreviousFile])
}
let parameters: Parameters = ["foo": "bar"]
 
Alamofire.download(urlString, method: .get, parameters: parameters, encoding: JSONEncoding.default, to: destination)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
print("进度: \(progress.fractionCompleted)")
}
.validate { request, response, temporaryURL, destinationURL in
// 自定义的校验闭包, 如今包含了 fileURL (必要时能够获取到错误信息)
return .success
}
.responseJSON { response in
debugPrint(response)
print(response.temporaryURL)
print(response.destinationURL)
}

Upload Request - Simple With URL string

// Alamofire 3
Alamofire.upload(.POST, urlString, data: data).response { request, response, data, error in
print(request)
print(response)
print(data)
print(error)
}
 
// Alamofire 4
Alamofire.upload(data, to: urlString).response { response in // 默认为 `.post` 方法
debugPrint(response)
}

Upload Request - Simple With URLRequest

// Alamofire 3
Alamofire.upload(urlRequest, file: fileURL).validate().responseData { response in
debugPrint(response)
}
 
// Alamofire 4
Alamofire.upload(fileURL, with: urlRequest).validate().responseData { response in
debugPrint(response)
}

Upload Request - Complex With URL string

// Alamofire 3
Alamofire.upload(.PUT, urlString, file: fileURL)
.progress { bytes, totalBytes, totalBytesExpected in
// 这里的进度是上传仍是下载的?
print("Bytes: \(bytesRead), Total Bytes: \(totalBytesRead), Total Bytes Expected: \(totalBytesExpectedToRead)")
}
.validate { request, response in
// 自定义的校验实现(获取不到服务端的数据)
return .success
}
.responseJSON { response in
debugPrint(response)
}
 
// Alamofire 4
Alamofire.upload(fileURL, to: urlString, method: .put)
.uploadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
print("上传进度: \(progress.fractionCompleted)")
}
.downloadProgress { progress in // 默认在主队列调用
print("下载进度: \(progress.fractionCompleted)")
}
.validate { request, response, data in
// 自定义的校验闭包, 如今加上了 `data` 参数(容许你提早转换数据以便在必要时挖掘到错误信息)
return .success
}
.responseJSON { response in
debugPrint(response)
}

就像你看到的, 有不少 API 破坏性的修改, 但经常使用的 API 仍是沿用了原来的设计, 但如今可以经过一行代码去生成更多更复杂的请求, 保持秩序的同时更加简洁.网络

URLStringConvertible 协议

URLStringConvertible 协议有两个很小的改变.session

URLConvertible

第一个没什么了不得的”大”改变就是 URLStringConvertible 已经被重命名为 URLConvertible. 在 3.x 里,URLStringConvertible 的定义是这样子的:闭包

public protocol URLStringConvertible {
var URLString: String { get }
}

如今在 Alamofire 4 里, URLConvertible 协议是这样定义的:

public protocol URLConvertible {
func asURL() throws -> URL
}

就像你看到的, URLString 属性彻底去掉了, 而后换成了可能会抛出异常的 asURL 方法. 为了解释这样作的缘由, 咱们先回顾一下.

Alamofire 一个最最多见的问题就是用户忘了对 URL 进行百分号编码, 致使 Alamofire 崩溃掉. 直到如今, 咱们(Alamofire 团队)的态度都是 Alamofire 就是这么设计的, 而你的 URL 必须遵照 RFC 2396 协议. 但这对于社区来讲并不那么好, 由于咱们更但愿 Alamofire 告诉咱们的 URL 是不合法的而不是直接 crash 掉.

如今, 回到新的 URLConvertible 协议. Alamofire 之因此不能安全地处理不合规范的 URL 字符串, 事实上是由于URLStringConvertible 安全性的缺失. Alamofire 不可能知道你是怎么造出一个不合法的 URL. 因此, 若是 URL 不能统统过 URLConvertible 被建立的话, 一个 AFError.invalidURL 的异常就会被抛出.

这个修改(以及其它不少修改都)可让 Alamofire 安全地处理不合理的 URL, 而且会在回调里抛出异常.

URLRequest Conformance

URLRequest 再也不遵照 URLStringConvertible, 如今是 URLConvertible. 但这也只是以前版本的一个延展而已, 并不那么重要. 不过这极可能会让 Alamofire 的 API 产生歧义. 所以, URLRequest 再也不遵照 URLStringConvertible.

这意味着你不能在代码里像这样子作了:

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

在 Alamofire 4里, 你应该这么作:

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

查看 PR-1505 以获取更多信息.

URLRequestConvertible

在 3.x 里, URLRequestConvertible 也会产生相同的歧义问题, 以前的 URLRequestConvertible 是这么定义的:

public protocol URLRequestConvertible {
var URLRequest: URLRequest { get }
}

如今, 在 Alamofire 4 里, 变成了这样子:

public protocol URLRequestConvertible {
func asURLRequest() throws -> URLRequest
}

就像看到的这样, URLRequest 属性被替换成了 asURLRequest 方法, 而且在生成 URLRequest 失败时会抛出异常.

这影响最大的多是采用了 Router (路由)设计的你, 若是你用了 Router, 那你就不得不去改变, 但会变得更好! 你须要去实现 asURLRequest 方法, 在必要的时候会抛出异常. 你再也不须要强制解包数据和参数, 或者在 do-catch 里构建一个 ParameterEncoding. 如今 Router 抛出的任何错误均可以由 Alamofire 帮你处理掉.

查看 PR-1505 以获取更多信息.

新功能

Request Adapter (请求适配器)

RequestAdapter 协议是 Alamofire 4 里的全新功能.

public protocol RequestAdapter {
func adapt(_ urlRequest: URLRequest) throws -> URLRequest
}

它可让每个 SessionManager 生成的 Request 都在生成以前被解析而且按照规则适配. 一个使用适配器很典型的场景就是给请求添加一个 Authorization 的请求头.

class AccessTokenAdapter: RequestAdapter {
private let accessToken: String
 
init(accessToken: String) {
self.accessToken = accessToken
}
 
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
var urlRequest = urlRequest
 
if urlRequest.urlString.hasPrefix("https://httpbin.org") {
urlRequest.setValue( "Bearer " + accessToken, forHTTPHeaderField: "Authorization")
}
 
return urlRequest
}
}
 
let sessionManager = SessionManager()
sessionManager.adapter = AccessTokenAdapter(accessToken: "1234")
 
sessionManager.request( "https://httpbin.org/get")

若是一个 Error 在适配过程当中产生的话, 它会逐层抛出, 最后传递到 Request 的请求回调里.

查看 PR-1450 获取更多信息.

Request Retrier (请求重连)

RequestRetrier 是 Alamofire 4 的另外一个全新协议.

public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void
 
public protocol RequestRetrier {
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)
}

它能够在 Request 遇到 Error的时候, 在指定的延迟以后从新发起.

class OAuth2Handler: RequestAdapter, RequestRetrier {
public func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: RequestRetryCompletion) {
if let response = request.task.response as? HTTPURLResponse, response.statusCode == 401 {
completion( true, 1.0) // 1秒后重试
} else {
completion( false, 0.0) // 不重连
}
}
}
 
let sessionManager = SessionManager()
sessionManager.retrier = OAuth2Handler()
 
sessionManager.request(urlString).responseJSON { response in
debugPrint(response)
}

重连器可让你在检测到 Request 完成而且完成全部 Validation 检测以后再考虑是否重试. 当 RequestAdapter和 RequestRetrier 一块儿使用的时候, 你能够给 OAuth1, OAuth2, Basic Auth 建立一套持续更新的校验系统(credential refresh systems), 甚至是快速重试的策略. 可能性是无限的. 想要获取更多关于这个话题的信息和例子, 请查看 README.

译者注: 这里没太能理解做者的意思, 翻译得很差, 直接放原文:
When using both the RequestAdapter and RequestRetrier protocols together, you can create credential refresh systems for OAuth1, OAuth2, Basic Auth and even exponential backoff retry policies.

查看 PR-1391 以及 PR-1450 获取更多信息.

Task Metrics

在 iOS, tvOS 10 和 macOS 10.12 里, 苹果引入了新的 URLSessionTaskMetrics API, task metrics 包含了一些 request 和 response 的统计信息, API 跟 Alamofire 的 Timeline 很像, 但提供了许多 Alamofire 里获取不到的统计信息. 咱们对这些新的 API 特别兴奋, 但把这些所有都暴露到每个 Response 类型里意味着这并不容易使用.

Alamofire.request(urlString).response { response in
debugPrint(response.metrics)
}

有一点很重要的是, 这些 API 只有在 iOS 和 tvOS 10+ 和 macOS 10.12+上才能使用. 因此它是依赖于运行设备的, 你可能须要作可行性检查.

Alamofire.request(urlString).response { response in
if #available(iOS 10.0, *) {
debugPrint(response.metrics)
}
}

查看 PR-1492 获取更多信息.

Updated Features 更新的功能

Alamofire 4 增强了现有的功能而且加入了不少新功能. 这一章节主要是大概地过一遍功能的更新和使用方式. 若是想要获取更多相关信息, 请点进连接查看相关的 pull request.

Errors 异常

Alamofire 4 加入了全新的异常系统, 采用了提案 SE-0112 里提出的新模式. 新的异常系统主要围绕 AFError, 一个继承了 Error 的枚举类型, 包含四个主要的 case.

  • .invalidURL(url: URLConvertible) - 建立 URL 失败的时候返回一个 URLConvertible 类型的值
  • .parameterEncodingFailed(reason: ParameterEncodingFailureReason) - 当其中一个参数编码出错的时候就会抛出错误并返回
  • .multipartEncodingFailed(reason: MultipartEncodingFailureReason) - multipart 编码出错就会抛出错误并返回
  • .responseValidationFailed(reason: ResponseValidationFailureReason) - 当调用 validate() 抛出错误时捕获而后抛出到外部.
  • .responseSerializationFailed(reason: ResponseSerializationFailureReason) - 返回的数据序列化出错时会抛出异常并返回.

每个 case 都包含了特定的异常理由, 而且异常理由又是另外一个带有具体错误信息的枚举类型. 这会让 Alamofire 更容易识别出错误的来源和缘由.

Alamofire.request(urlString).responseJSON { response in
guard case let .failure(error) = response.result else { return }
 
if let error = error as? AFError {
switch error {
case .invalidURL(let url):
print("无效 URL: \(url) - \(error.localizedDescription)")
case .parameterEncodingFailed(let reason):
print("参数编码失败: \(error.localizedDescription)")
print("失败理由: \(reason)")
case .multipartEncodingFailed(let reason):
print("Multipart encoding 失败: \(error.localizedDescription)")
print("失败理由: \(reason)")
case .responseValidationFailed(let reason):
print("Response 校验失败: \(error.localizedDescription)")
print("失败理由: \(reason)")
 
switch reason {
case .dataFileNil, .dataFileReadFailed:
print("没法读取下载文件")
case .missingContentType(let acceptableContentTypes):
print("文件类型不明: \(acceptableContentTypes)")
case .unacceptableContentType(let acceptableContentTypes, let responseContentType):
print("文件类型: \(responseContentType) 没法读取: \(acceptableContentTypes)")
case .unacceptableStatusCode(let code):
print("请求返回状态码出错: \(code)")
}
case .responseSerializationFailed(let reason):
print("请求返回内容序列化失败: \(error.localizedDescription)")
print("失败理由: \(reason)")
}
 
print("错误: \(error.underlyingError)")
} else if let error = error as? URLError {
print("URL 错误: \(error)")
} else {
print("未知错误: \(error)")
}
}

新的设计给你的处理方式更多的自由, 能够在你须要的时候深刻到最具体的 error. 这也会让本来要四处应对NSError 的开发者更加轻松地完成工做. 在 Alamofire 里经过使用自定义的 Error 类型, 咱们能够看到 Result 和Response 的泛型参数缩减到了只有一个, 简化了返回数据序列化的逻辑.

查看 PR-1419 获取更多信息.

Parameter Encoding Protocol 参数编码的协议

ParameterEncoding 枚举类型在过去两年很好地解决了问题. 但咱们在 Alamofire 4 里想要定位的时候却感受到了一些局限.

  • .url 总让人有点迷惑, 由于它是一个 HTTP 协议定义的地址
  • .urlEncodedInURL 跟 .url 老是会混淆起来, 让人分不清它们行为的区别
  • .JSON 和 .PropertyList 编码不能自定义编码格式或者写入的方式
  • .Custom 编码对于用户来讲太难掌握

由于这些缘由, 咱们决定在 Alamofire 4 把这个枚举去掉! 如今, ParameterEncoding 变成了一个协议, 加入了Parameters 的类型别名去建立你的参数字典, 而且经过遵照这个协议创建了三个编码结构体 URLEncoding,JSONEncoding 和 PropertyList.

public typealias Parameters = [String: Any]
 
public protocol ParameterEncoding {
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
}

URL Encoding (参数编码)

新的 URLEncoding 结构体包含了一个 Destination 枚举, 支持三种类型的目标编码

  • .methodDependent - 对于 GETHEAD 和 DELETE 方法使用 query 字符串, 而别的 HTTP 方法则会编码为 HTTP body.
  • .queryString - 设置或者往现有的 queryString 里增长内容
  • .httpBody - 设置请求的 HTTP body 内容

这些目标编码格式会让你更容易控制 URLRequest 的参数编码方式. 建立请求依旧使用和以前同样的方式, 无论编码的形式怎样, 都会保持与以前同样的默认行为.

let parameters: Parameters = ["foo": "bar"]
 
Alamofire.request(urlString, parameters: parameters) // Encoding => URLEncoding(destination: .methodDependent)
Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding(destination: .queryString))
Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding(destination: .httpBody))
 
// Static convenience properties (we'd like to encourage everyone to use this more concise form)
// 便利的静态属性 (咱们想鼓励你们使用这种更简洁的形式)
Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding.default)
Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding.queryString)
Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding.httpBody)

JSON Encoding (JSON 编码)

新的 JSONEncoding 结构体开放了让你自定义 JSON 写入形式的接口.

let parameters: Parameters = ["foo": "bar"]
 
Alamofire.request(urlString, parameters: parameters, encoding: JSONEncoding(options: []))
Alamofire.request(urlString, parameters: parameters, encoding: JSONEncoding(options: .prettyPrinted))
 
// Static convenience properties (we'd like to encourage everyone to use this more concise form)
// 便利的静态属性 (咱们想鼓励你们使用这种更简洁的形式)
Alamofire.request(urlString, parameters: parameters, encoding: JSONEncoding.default)
Alamofire.request(urlString, parameters: parameters, encoding: JSONEncoding.prettyPrinted)

Property List Encoding (属性列表编码)

新的 PropertyListEncoding 结构体容许自定义 plist 的格式和写入选项

let parameters: Parameters = ["foo": "bar"]
 
Alamofire.request(urlString, parameters: parameters, encoding: PropertyListEncoding(format: .xml, options: 0))
Alamofire.request(urlString, parameters: parameters, encoding: PropertyListEncoding(format: .binary, options: 0))
 
// Static convenience properties (we'd like to encourage everyone to use this more concise form)
// 便利的静态属性 (咱们想鼓励你们使用这种更简洁的形式)
Alamofire.request(urlString, parameters: parameters, encoding: PropertyListEncoding.xml)
Alamofire.request(urlString, parameters: parameters, encoding: PropertyListEncoding.binary)

Custom Encoding 自定义编码

创建一个自定义的 ParameterEncoding 只要遵照这个协议创建类型便可. 想要获取更多相关例子, 请查看下面的 README

查看 PR-1465 获取更多信息

Request Subclasses (Request 的子类)

在 Alamofire 4, requestdownloadupload 和 stream 的 API 不会再返回 Request, 他们会返回特定的Request 子类. 有下面几个引导咱们作出这个改变的现实缘由和社区的疑问:

  • Progress: progress 方法的行为会在 upload 请求里会很容易让人迷惑.
    • progress 在一个 upload 请求里返回的是什么? 上传的进度? 仍是返回内容的下载进度?
    • 若是都返回, 那咱们怎么区分他们, 在何时能知道是到底返回的是哪个?
  • Response Serializers: 返回内容的序列化是为了 data 和 upload 请求设计的, donwload 和 stream 请求并不须要序列化.
    • 你要怎么才能在下载完成时获取到文件的地址?
    • responseDataresponseString 和 responseJSON 对于一个 donwload 请求来讲意味着什么? stream 请求呢?

Alamofire 4 如今有四个 Request 的子类, 而且每一个字类都有一些特有的 API. 这样就可让每个子类可以经过创建 extension 来定制特定类型的请求.

open class Request {
// 包含了共有的属性, 验证, 和状态方法
// 遵照 CustomStringConvertible 和 CustomDebugStringConvertible
}
 
open class DataRequest: Request {
// 包含了数据流(不要跟 StreamRequest 混淆)和下载进度的方法
}
 
open class DownloadRequest: Request {
// 包含了下载位置和选项, 已下载的数据以及进度方法
}
 
open class UploadRequest: DataRequest {
// 继承了全部 DataRequest 的方法, 而且包含了上传进度的方法
}
 
open class StreamRequest: Request {
// 只继承了 Request, 目前暂时没有任何自定义的 API
}

经过这样的切分, Alamofire 如今能够为每个类型的请求自定义相关的 API. 这会覆盖到全部可能的需求, 但让咱们花点时间来仔细了解一下这会如何改变进度汇报和下载地址.

查看 PR-1455 获取更多信息

Download and Upload Progress (下载和上传你进度)

Data, download 和 upload 请求的进度汇报系统彻底从新设计了一遍. 每个请求类型都包含有一个闭包, 每当进度更新的时候, 就会调用闭包而且传入 Progress 类型的参数. 这个闭包会在指定的队列被调用, 默认为主队列.

Data Request 进度

Alamofire.request(urlString)
.downloadProgress { progress in
// 默认在主队列调用
print("下载进度: \(progress.fractionCompleted)")
}
.responseJSON { response in
debugPrint(response)
}

Download Request 进度

Alamofire.download(urlString, to: destination)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
// 在 .utility 队列里调用
print("下载进度: \(progress.fractionCompleted)")
}
.responseJSON { response in
debugPrint(response)
}

Upload Request 进度

Alamofire.upload(data, to: urlString, withMethod: .post)
.uploadProgress { progress in
// 默认在主队列调用
print("上传进度: \(progress.fractionCompleted)")
}
.downloadProgress { progress in
// 默认在主队列调用
print("下载进度: \(progress.fractionCompleted)")
}
.responseData { response in
debugPrint(response)
}

如今很容易就能够区分开 upload request 里的上传和下载进度.

查看 PR-1455 获取更多信息.

Download File Destinations 文件下载地址

在 Alamofire 3.x, 顺利完成的 download requests 老是会在 destination 回调里把临时文件移动到最终目标文件夹里. 这很方便, 但也同时带来了几个限制:

  • Forced - API 强制你去提供一个 destination 闭包来移动文件, 即便你验证事后不想移动文件了.
  • Limiting - 没有任何方式能够去调整文件系统移动文件的优先级别.
    • 若是你须要在移动到目标文件夹以前删掉以前存在的文件呢?
    • 若是你须要在移动临时文件以前建立目录呢?

这些限制都会在 Alamofire 4 里都不复存在. 首先是 optional 的 destination 闭包. 如今, destination 默认为 nil, 意味着文件系统不会移动文件, 而且会返回临时文件的 URL.

Alamofire.download(urlString).responseData { response in
print("临时文件的 URL: \(response.temporaryURL)")
}

咱们将会恢复 DownloadResponse 类型, 更多详细信息请查看 Reponse Serializers 章节.

Download Options 下载选项

另一个主要的改变是 destination 闭包里面加上了下载选项, 让你能够进行更多文件系统操做. 为了达到目的, 咱们创建了一个 DownloadOptions 类型而且添加到 DownloadFileDestination 闭包里.

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

现阶段支持的两个 DownloadOptions 是:

  • .createIntermediateDirectories - 若是有指定的下载地址的话, 会为下载地址建立相应的目录
  • .removePreviousFile - 若是有指定的下载地址的话, 会自动替代掉同名文件

这两个选项能够像下面这样用:

let destination: DownloadRequest.DownloadFileDestination = { _, _ in
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
 
Alamofire.download(urlString, to: destination).response { response in
debugPrint(response)
}

若是一个异常在文件系统操做时抛出的话, DownloadResponse 的 error 就会是 URLError 类型.

查看 PR-1462 获取更多信息.

Response Validation 数据验证

在 Alamofire 4 里有几个能够增强数据验证系统的地方. 包括了:

  • Validation 回调闭包里传入的 data
  • Request 子类能够自定义数据验证系统, 例如 download 请求里的 temporaryURL 和 destinationURL 暴露到了回调闭包里

经过继承 Request, 每个 Request 的子类均可以自定义一套数据验证的闭包(typealias)和请求的 API.

Data Request 数据请求

DataRequest (UploadRequest 的父类)暴露出来的 Validation 目前是这样定义的:

extension DataRequest {
public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
}

直接在闭包里把 Data? 暴露出来, 你就不须要再给 Request 增长一个 extension 去访问这个属性了. 如今你能够直接这样子作:

Alamofire.request(urlString)
.validate { request, response, data in
guard let data = data else { return .failure(customError) }
 
// 1) 验证返回的数据保证接下来的操做不会出错
// 2) 若是验证失败, 你能够把错误信息返回出去, 甚至加上自定义的 error
 
return .success
}
.response { response in
debugPrint(response)
}

Download Request 下载请求

DownloadRequest 里的 Validation 闭包跟 DataRequest 里的很像, 但为了下载任务作了更多的定制.

extension DownloadRequest {
public typealias Validation = (
_ request: URLRequest?,
_ response: HTTPURLResponse,
_ temporaryURL: URL?,
_ destinationURL: URL?)
-> ValidationResult
}

temporaryURL 和 destinationURL 参数如今让你能够在闭包内直接获取到服务器返回的数据. 这可让你校验下载好的文件, 在有须要的时候能够抛出一个自定义的错误.

Alamofire.download(urlString)
.validate { request, response, temporaryURL, destinationURL in
guard let fileURL = temporaryURL else { return .failure(customError) }
 
do {
let _ = try Data(contentsOf: fileURL)
return .success
} catch {
return .failure(customError)
}
}
.response { response in
debugPrint(response)
}

经过直接在闭包里暴露服务器返回的数据, 这里面的全部异常均可以在 Validation 闭包里捕获到, 而且能够自定义错误信息. 若是这里获取到的信息和 response 序列化回调里同样的话, response 能够用来处理错误信息而不是简单地把逻辑赋值过来. 具体的例子, 请查看下面的 README.

查看 PR-1461 获取更多信息.

Response Serializers 返回数据序列化

Alamofire 3.x 里的序列化系统有这么几个限制:

  • 序列化的 API 能够用在 download 和 stream 请求里, 但却会致使未知的行为发生
    • 怎么在下载成功时获取到文件 URL?
    • responseDataresponseString 或者 responseJSON 会在 donwload 请求里产生怎样的行为? stream 请求呢?
  • response API 返回四个参数而不是封装到一个 Response 类型里.
    • 最大的问题是 API 任何改变都会致使前面行为的变化.
    • 在序列化和反序列化的 API 之间切换会让人迷惑, 同时致使难以 debug 的编译错误.

就像你看到的, Alamofire 3.x 的这一套序列化系统有这么多限制. 因此, 在 Alamofire 4里, Request 类型首先被切分到各个子类里, 这么作给自定义序列化方式, 和自定义 API 留下了空间. 在咱们更深刻了解序列化方式以前, 咱们先了解一下新的 Response 类型

Default Data Response

DefaultDataResponse 表明了未被序列化的服务器返回数据. Alamofire 没有作任何处理过的, 只是纯粹地从SessionDelegate 里获取信息而且包装在一个结构体里面返回.

public struct DefaultDataResponse {
public let request: URLRequest?
public let response: HTTPURLResponse?
public let data: Data?
public let error: Error?
public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
}

下面是你会得到 DataRequest.response 的一种返回.

Alamofire.request(urlString).response { response in
debugPrint(response)
}
 
Alamofire.upload(file, to: urlString).response { response in
debugPrint(response)
}

Data Response

泛型 DataResponse 类型跟 Alamofire 3.x 里的 Response 同样, 但内部重构而且包含了新的 metrics 变量.

public struct DataResponse<Value> {
public let request: URLRequest?
public let response: HTTPURLResponse?
public let data: Data?
public let result: Result<Value>
public let timeline: Timeline
public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
}

使用 DataRequest 和 UploadRequest, 你能够像以前(3.x)那样使用 response 序列化的 API

Alamofire.request(urlString).responseJSON { response in
debugPrint(response)
print(response.result.isSuccess)
}
 
Alamofire.upload(fileURL, to: urlString).responseData { response in
debugPrint(response)
print(response.result.isSuccess)
}

Default Download Response 默认下载请求的 Response 类型

由于 donwload 请求跟 data 和 upload 请求很不同, 因此 Alamofire 4 包含了自定义的 donwload Response 类型.DefaultDownloadResponse 类型表明未序列化的返回数据, 包含了全部 SessionDelegate 信息的结构体.

public struct DefaultDownloadResponse {
public let request: URLRequest?
public let response: HTTPURLResponse?
public let temporaryURL: URL?
public let destinationURL: URL?
public let resumeData: Data?
public let error: Error?
public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
}

DefaultDownloadResponse 类型在使用新的 DownloadRequest.response API 时就会被返回.

Alamofire.download(urlString).response { response in
debugPrint(response)
print(response.temporaryURL)
}

Download Response

新的泛型 DownloadResponse 跟 DataResponse 很像, 但包含了 download 请求特有的信息. DownloadResponse 类型在使用 DownloadRequest 时就会被返回. 这些新的 API 一样也适用于 DataRequest, 同样可以获取临时目录的 url 和目标目录的 url.

Alamofire.download(urlString, to: destination)
.responseData { response in
debugPrint(response)
}
.responseString { response in
debugPrint(response)
}
.responseJSON { response in
debugPrint(response)
}
.responsePropertyList { response in
debugPrint(response)
}

新的序列化 API 让文件下载和序列化更加容易完成.

Custom Response Serializers 自定义序列化

若是你已经建立了自定义的序列化, 你也许会想要拓展支持 data 和 download 请求, 就像咱们在 Alamofire 序列化 API 里面作的同样.. 若是你决定这么作, 能够仔细看一下 Alamofire 怎么在几种 Request 类型里共享序列化方法, 而后把实现写到 Request 里就能够了. 这可让咱们 DRY up 逻辑而且避免重复的代码.(Don’t repeat yourself)

 

原文连接:https://kemchenj.github.io/2017/01/06/2016-11-30/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

相关文章
相关标签/搜索