Alamofire 4.0 是 Alamofire 最新的一个大版本更新, 一个基于 Swift 的 iOS, tvOS, macOS, watchOS 的 HTTP 网络库. 做为一个大版本更新, 就像语义上那样, 4.0 的 API 引入了一些破坏性修改.html
这篇导引旨在帮助你们从 Alamofire 3.x 平滑过渡到最新版本, 同时也解释一下新的设计和结构, 以及功能上的更新.git
那些想要在 iOS 8 或者 macOS 10.9 使用 Alamofire 的, 请使用 3.x 版本的最新 release(同时支持 Swift 2.2以及2.3)github
AFError
类型.RequestAdapter
协议: 能够在初始化 Request
的时候进行快速便捷的适配, 例如在请求头里加入Authorization
RequestRetrier
协议: 能够检测而且重试失败的 Request
, 甚至能够本身根据一系列需求去构建一套验证的解决方案( OAuth1, OAuth2, xAuth, Basic Auth 之类的).Parameter Encoding
协议: 取代掉以前的 ParameterEncoding
枚举, 容许你更简单的拓展和自定义, 而且在错误时抛出异常, 而不是简单的返回一个元组.DataRequest
, DownloadRequest
, UploadRequest
和 StreamRequest
, 实现了特定的进度, 验证和序列化的 API 以及各自的 Request
类型.downloadProgress
和 uploadProgress
, 支持 progress
和 Int64
类型, 而且会在指定的线程运行, 默认为主线程.data
或者 temporaryURL
和 destinationURL
均可以使用内联的闭包去转化服务器返回的错误信息destinationURL
, 还得建立临时文件夹, 删掉以前的文件.Response
类型: 统一 response 的 API, 而且为全部下载任务提供 temporaryURL
和 downloadURL
, 以及其它新平台上的任务属性.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 的等效代码对比.服务器
// 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)
}
|
// 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)
}
|
// 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)
}
|
// 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)
}
|
// 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)
}
|
// 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)
}
|
// 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)
}
|
// 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
协议有两个很小的改变.session
第一个没什么了不得的”大”改变就是 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
再也不遵照 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 以获取更多信息.
在 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 以获取更多信息.
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 获取更多信息.
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 theRequestAdapter
andRequestRetrier
protocols together, you can create credential refresh systems for OAuth1, OAuth2, Basic Auth and even exponential backoff retry policies.
在 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 获取更多信息.
Alamofire 4 增强了现有的功能而且加入了不少新功能. 这一章节主要是大概地过一遍功能的更新和使用方式. 若是想要获取更多相关信息, 请点进连接查看相关的 pull request.
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 获取更多信息.
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
}
|
新的 URLEncoding
结构体包含了一个 Destination
枚举, 支持三种类型的目标编码
.methodDependent
- 对于 GET
, HEAD
和 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)
|
新的 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)
|
新的 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)
|
创建一个自定义的 ParameterEncoding
只要遵照这个协议创建类型便可. 想要获取更多相关例子, 请查看下面的 README
查看 PR-1465 获取更多信息
在 Alamofire 4, request
, download
, upload
和 stream
的 API 不会再返回 Request
, 他们会返回特定的Request
子类. 有下面几个引导咱们作出这个改变的现实缘由和社区的疑问:
progress
方法的行为会在 upload 请求里会很容易让人迷惑.
progress
在一个 upload 请求里返回的是什么? 上传的进度? 仍是返回内容的下载进度?responseData
, responseString
和 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 获取更多信息
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 获取更多信息.
在 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 章节.
另一个主要的改变是 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 获取更多信息.
在 Alamofire 4 里有几个能够增强数据验证系统的地方. 包括了:
Validation
回调闭包里传入的 data
Request
子类能够自定义数据验证系统, 例如 download 请求里的 temporaryURL
和 destinationURL
暴露到了回调闭包里经过继承 Request
, 每个 Request
的子类均可以自定义一套数据验证的闭包(typealias)和请求的 API.
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)
}
|
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 获取更多信息.
Alamofire 3.x 里的序列化系统有这么几个限制:
responseData
, responseString
或者 responseJSON
会在 donwload 请求里产生怎样的行为? stream 请求呢?response
API 返回四个参数而不是封装到一个 Response
类型里.
就像你看到的, Alamofire 3.x 的这一套序列化系统有这么多限制. 因此, 在 Alamofire 4里, Request
类型首先被切分到各个子类里, 这么作给自定义序列化方式, 和自定义 API 留下了空间. 在咱们更深刻了解序列化方式以前, 咱们先了解一下新的 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)
}
|
泛型 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)
}
|
由于 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)
}
|
新的泛型 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 让文件下载和序列化更加容易完成.
若是你已经建立了自定义的序列化, 你也许会想要拓展支持 data 和 download 请求, 就像咱们在 Alamofire 序列化 API 里面作的同样.. 若是你决定这么作, 能够仔细看一下 Alamofire 怎么在几种 Request
类型里共享序列化方法, 而后把实现写到 Request
里就能够了. 这可让咱们 DRY up 逻辑而且避免重复的代码.(Don’t repeat yourself)