Alamofire 4.0 迁移指南
译者注:最近打算把公司项目迁移到 Swift 3.0,顺手把 Alamofire 4.0 的迁移指南翻译了,之前虽然读过一部分源码,但还是看到了很多新东西,新的 Adapter 和 Retrier 我都打算用到项目里,希望大家看完也能够有收获. Alamofire 4.0 是 Alamofire 最新的一个大版本更新,一个基于 Swift 的 iOS,tvOS,macOS,watchOS 的 HTTP 网络库. 作为一个大版本更新,就像语义上那样,4.0 的 API 引入了一些破坏性修改. 这篇导引旨在帮助大家从 Alamofire 3.x 平滑过渡到最新版本,同时也解释一下新的设计和结构,以及功能上的更新. 要求
那些想要在 iOS 8 或者 macOS 10.9 使用 Alamofire 的,请使用 3.x 版本的最新 release(同时支持 Swift 2.2以及2.3) 升级的好处
API 破坏性的修改Alamofire 4 跟进了 Swift 3 里所有的修改,包括 API 设计规范. 因此,几乎所有 Alamofire 的 API 都进行了一定程度的修改. 我们没办法把这些修改全部在文档里列出来,所以我们会把最常用的那些 API 列出来,然后告诉大家这些 API 进行了哪些修改,而不是指望那些有时帮倒忙的编译错误提示. 命名空间的修改一些常用的类移到了全局命名空间成为一级类,让他们更容易使用.
我们也重新调整了文件结构和组织模式,帮助更好的跟进代码. 我们希望这可以让更多用户去了解内部结构和 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,encoding: JSONEncoding.default) .downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in print("进度: (progress.fractionCompleted)") } .validate { request,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,destination: destination).response { request,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,encoding: .JSON,to: destination) .progress { bytesRead,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,encoding: JSONEncoding.default,to: destination) .downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in print("进度: (progress.fractionCompleted)") } .validate { request,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,data: data).response { request,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,file: fileURL) .progress { bytes,totalBytes,totalBytesExpected in // 这里的进度是上传还是下载的? print("Bytes: (bytesRead),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,现在加上了 `data` 参数(允许你提前转换数据以便在必要时挖掘到错误信息) return .success } .responseJSON { response in debugPrint(response) } 就像你看到的,有很多 API 破坏性的修改,但常用的 API 还是沿用了原来的设计,但现在能够通过一行代码去生成更多更复杂的请求,保持秩序的同时更加简洁. URLStringConvertible 协议
URLConvertible第一个没什么了不起的"大"改变就是 public protocol URLStringConvertible { var URLString: String { get } } 现在在 Alamofire 4 里, public protocol URLConvertible { func asURL() throws -> URL } 就像你看到的, Alamofire 一个最最常见的问题就是用户忘了对 URL 进行百分号编码,导致 Alamofire 崩溃掉. 直到现在,我们(Alamofire 团队)的态度都是 Alamofire 就是这么设计的,而你的 URL 必须遵守 RFC 2396 协议. 但这对于社区来说并不那么好,因为我们更希望 Alamofire 告诉我们的 URL 是不合法的而不是直接 crash 掉. 现在,回到新的 这个修改(以及其它很多修改都)可以让 Alamofire 安全地处理不合理的 URL,并且会在回调里抛出异常. URLRequest Conformance
这意味着你不能在代码里像这样子做了: 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
URLRequestConvertible在 3.x 里, public protocol URLRequestConvertible { var URLRequest: URLRequest { get } } 现在,在 Alamofire 4 里,变成了这样子: public protocol URLRequestConvertible { func asURLRequest() throws -> URLRequest } 就像看到的这样, 这影响最大的可能是采用了
新功能Request Adapter (请求适配器)
public protocol RequestAdapter { func adapt(_ urlRequest: URLRequest) throws -> URLRequest } 它可以让每一个 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") 如果一个
Request Retrier (请求重连)
public typealias RequestRetryCompletion = (_ shouldRetry: Bool,_ timeDelay: TimeInterval) -> Void public protocol RequestRetrier { func should(_ manager: SessionManager,retry request: Request,with error: Error,completion: @escaping RequestRetryCompletion) } 它可以在 class OAuth2Handler: RequestAdapter,RequestRetrier { public func should(_ manager: SessionManager,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) } 重连器可以让你在检测到
Task Metrics在 iOS,tvOS 10 和 macOS 10.12 里,苹果引入了新的 URLSessionTaskMetrics API,task metrics 包含了一些 request 和 response 的统计信息,API 跟 Alamofire 的 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) } }
Updated Features 更新的功能Alamofire 4 加强了现有的功能并且加入了很多新功能. 这一章节主要是大概地过一遍功能的更新和使用方式. 如果想要获取更多相关信息,请点进链接查看相关的 pull request. Errors 异常Alamofire 4 加入了全新的异常系统,采用了提案 SE-0112 里提出的新模式. 新的异常系统主要围绕
每一个 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. 这也会让原本要四处应对
Parameter Encoding Protocol 参数编码的协议
因为这些原因,我们决定在 Alamofire 4 把这个枚举去掉! 现在, public typealias Parameters = [String: Any] public protocol ParameterEncoding { func encode(_ urlRequest: URLRequestConvertible,with parameters: Parameters?) throws -> URLRequest } URL Encoding (参数编码)新的
这些目标编码格式会让你更容易控制 let parameters: Parameters = ["foo": "bar"] Alamofire.request(urlString,parameters: parameters) // Encoding => URLEncoding(destination: .methodDependent) Alamofire.request(urlString,encoding: URLEncoding(destination: .queryString)) Alamofire.request(urlString,encoding: URLEncoding(destination: .httpBody)) // Static convenience properties (we'd like to encourage everyone to use this more concise form) // 便利的静态属性 (我们想鼓励大家使用这种更简洁的形式) Alamofire.request(urlString,encoding: URLEncoding.default) Alamofire.request(urlString,encoding: URLEncoding.queryString) Alamofire.request(urlString,encoding: URLEncoding.httpBody) JSON Encoding (JSON 编码)新的 let parameters: Parameters = ["foo": "bar"] Alamofire.request(urlString,encoding: JSONEncoding(options: [])) Alamofire.request(urlString,encoding: JSONEncoding(options: .prettyPrinted)) // Static convenience properties (we'd like to encourage everyone to use this more concise form) // 便利的静态属性 (我们想鼓励大家使用这种更简洁的形式) Alamofire.request(urlString,encoding: JSONEncoding.default) Alamofire.request(urlString,encoding: JSONEncoding.prettyPrinted) Property List Encoding (属性列表编码)新的 let parameters: Parameters = ["foo": "bar"] Alamofire.request(urlString,encoding: PropertyListEncoding(format: .xml,options: 0)) Alamofire.request(urlString,encoding: PropertyListEncoding(format: .binary,options: 0)) // Static convenience properties (we'd like to encourage everyone to use this more concise form) // 便利的静态属性 (我们想鼓励大家使用这种更简洁的形式) Alamofire.request(urlString,encoding: PropertyListEncoding.xml) Alamofire.request(urlString,encoding: PropertyListEncoding.binary) Custom Encoding 自定义编码建立一个自定义的
Request Subclasses (Request 的子类)在 Alamofire 4,
Alamofire 4 现在有四个 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. 这会覆盖到所有可能的需求,但让我们花点时间来仔细了解一下这会如何改变进度汇报和下载地址.
Download and Upload Progress (下载和上传你进度)Data,download 和 upload 请求的进度汇报系统完全重新设计了一遍. 每一个请求类型都包含有一个闭包,每当进度更新的时候,就会调用闭包并且传入 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,withMethod: .post) .uploadProgress { progress in // 默认在主队列调用 print("上传进度: (progress.fractionCompleted)") } .downloadProgress { progress in // 默认在主队列调用 print("下载进度: (progress.fractionCompleted)") } .responseData { response in debugPrint(response) } 现在很容易就可以区分开 upload request 里的上传和下载进度.
Download File Destinations 文件下载地址在 Alamofire 3.x,顺利完成的 download requests 总是会在
这些限制都会在 Alamofire 4 里都不复存在. 首先是 optional 的 destination 闭包. 现在, Alamofire.download(urlString).responseData { response in print("临时文件的 URL: (response.temporaryURL)") }
Download Options 下载选项另外一个主要的改变是 destination 闭包里面加上了下载选项,让你可以进行更多文件系统操作. 为了达到目的,我们建立了一个 public typealias DownloadFileDestination = ( _ temporaryURL: URL,_ response: HTTPURLResponse) -> (destinationURL: URL,options: DownloadOptions) 现阶段支持的两个
这两个选项可以像下面这样用: let destination: DownloadRequest.DownloadFileDestination = { _,_ in return (fileURL,[.removePreviousFile,.createIntermediateDirectories]) } Alamofire.download(urlString,to: destination).response { response in debugPrint(response) } 如果一个异常在文件系统操作时抛出的话,
Response Validation 数据验证在 Alamofire 4 里有几个可以加强数据验证系统的地方. 包括了:
通过继承 Data Request 数据请求
extension DataRequest { public typealias Validation = (URLRequest?,HTTPURLResponse,Data?) -> ValidationResult } 直接在闭包里把 Alamofire.request(urlString) .validate { request,data in guard let data = data else { return .failure(customError) } // 1) 验证返回的数据保证接下来的操作不会出错 // 2) 如果验证失败,你可以把错误信息返回出去,甚至加上自定义的 error return .success } .response { response in debugPrint(response) } Download Request 下载请求
extension DownloadRequest { public typealias Validation = ( _ request: URLRequest?,_ response: HTTPURLResponse,_ temporaryURL: URL?,_ destinationURL: URL?) -> ValidationResult }
Alamofire.download(urlString) .validate { request,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) } 通过直接在闭包里暴露服务器返回的数据,这里面的所有异常都可以在
Response Serializers 返回数据序列化Alamofire 3.x 里的序列化系统有这么几个限制:
就像你看到的,Alamofire 3.x 的这一套序列化系统有这么多限制. 所以,在 Alamofire 4里, Default Data Response
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 } } 下面是你会获得 Alamofire.request(urlString).response { response in debugPrint(response) } Alamofire.upload(file,to: urlString).response { response in debugPrint(response) } Data Response泛型 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 } } 使用 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 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 } }
Alamofire.download(urlString).response { response in debugPrint(response) print(response.temporaryURL) } Download Response新的泛型 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 怎么在几种
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |