Alamofire 4.0 迁移指南

原文: Alamofire 4.0 Migration Guide
作者: cnoon
译者: kemchenj

译者注:最近打算把公司项目迁移到 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.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)

升级的好处

  • 完美适配 Swift 3: 跟进了新的 API 设计规范.

  • 新的错误处理系统: 根据提案 SE-0112 里的新模式,新增了 AFError 类型.

  • 新的 RequestAdapter 协议: 可以在初始化 Request 的时候进行快速便捷的适配,例如在请求头里加入 Authorization

  • 新的 RequestRetrier 协议: 可以检测并且重试失败的 Request,甚至可以自己根据一系列需求去构建一套验证的解决方案( OAuth1,OAuth2,xAuth,Basic Auth 之类的).

  • 新的 Parameter Encoding 协议: 取代掉之前的 ParameterEncoding 枚举,允许你更简单的拓展和自定义,并且在错误时抛出异常,而不是简单的返回一个元组.

  • 新的请求类型: 包括 DataRequest,DownloadRequest,UploadRequestStreamRequest,实现了特定的进度,验证和序列化的 API 以及各自的 Request 类型.

  • 新的进度 API: 包括 downloadProgressuploadProgress,支持 progressInt64 类型,并且会在指定的线程运行,默认为主线程.

  • 更强大的数据验证: 在验证失败的时候,包括 data 或者 temporaryURLdestinationURL 都可以使用内联的闭包去转化服务器返回的错误信息

  • 新的下载地址处理: 你可以获得完整的控制权,而不是像之前那样只是提供一个 destinationURL,还得创建临时文件夹,删掉之前的文件.

  • 新的 Response 类型: 统一 response 的 API,并且为所有下载任务提供 temporaryURLdownloadURL,以及其它新平台上的任务属性.

API 破坏性的修改

Alamofire 4 跟进了 Swift 3 里所有的修改,包括 API 设计规范. 因此,几乎所有 Alamofire 的 API 都进行了一定程度的修改. 我们没办法把这些修改全部在文档里列出来,所以我们会把最常用的那些 API 列出来,然后告诉大家这些 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,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 协议

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

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,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 检测之后再考虑是否重试. 当 RequestAdapterRequestRetrier 一起使用的时候,你可以给 OAuth1,Basic Auth 创建一套持续更新的校验系统(credential refresh systems),甚至是快速重试的策略. 可能性是无限的. 想要获取更多关于这个话题的信息和例子,请查看 README.

译者注: 这里没太能理解作者的意思,翻译得不好,直接放原文:
When using both the RequestAdapter and RequestRetrier protocols together,you can create credential refresh systems for OAuth1,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 类型,我们可以看到 ResultResponse 的泛型参数缩减到了只有一个,简化了返回数据序列化的逻辑.

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

Parameter Encoding Protocol 参数编码的协议

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

  • .url 总让人有点迷惑,因为它是一个 HTTP 协议定义的地址

  • .urlEncodedInURL.url 总是会混淆起来,让人分不清它们行为的区别

  • .JSON.PropertyList 编码不能自定义编码格式或者写入的方式

  • .Custom 编码对于用户来说太难掌握

因为这些原因,我们决定在 Alamofire 4 把这个枚举去掉! 现在,ParameterEncoding 变成了一个协议,加入了 Parameters 的类型别名去创建你的参数字典,并且通过遵守这个协议建立了三个编码结构体 URLEncoding,JSONEncodingPropertyList.

public typealias Parameters = [String: Any]

public protocol ParameterEncoding {
    func encode(_ urlRequest: URLRequestConvertible,with parameters: Parameters?) throws -> URLRequest
}

URL Encoding (参数编码)

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

  • .methodDependent - 对于 GET,HEADDELETE 方法使用 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,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 编码)

新的 JSONEncoding 结构体开放了让你自定义 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 (属性列表编码)

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

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 自定义编码

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

查看 PR-1465 获取更多信息

Request Subclasses (Request 的子类)

在 Alamofire 4,request,download,uploadstream 的 API 不会再返回 Request,他们会返回特定的 Request 子类. 有下面几个引导我们做出这个改变的现实原因和社区的疑问:

  • Progress: progress 方法的行为会在 upload 请求里会很容易让人迷惑.

    • progress 在一个 upload 请求里返回的是什么? 上传的进度? 还是返回内容的下载进度?

    • 如果都返回,那我们怎么区分他们,在什么时候能知道是到底返回的是哪一个?

  • Response Serializers: 返回内容的序列化是为了 data 和 upload 请求设计的,donwload 和 stream 请求并不需要序列化.

    • 你要怎么才能在下载完成时获取到文件的地址?

    • responseData,responseStringresponseJSON 对于一个 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,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)
}

如果一个异常在文件系统操作时抛出的话,DownloadResponseerror 就会是 URLError 类型.

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

Response Validation 数据验证

在 Alamofire 4 里有几个可以加强数据验证系统的地方. 包括了:

  • Validation 回调闭包里传入的 data

  • Request 子类可以自定义数据验证系统,例如 download 请求里的 temporaryURLdestinationURL 暴露到了回调闭包里

通过继承 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,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
}

temporaryURLdestinationURL 参数现在让你可以在闭包内直接获取到服务器返回的数据. 这可以让你校验下载好的文件,在有需要的时候可以抛出一个自定义的错误.

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

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

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

Response Serializers 返回数据序列化

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

  • 序列化的 API 可以用在 download 和 stream 请求里,但却会导致未知的行为发生

    • 怎么在下载成功时获取到文件 URL?

    • responseData,responseString 或者 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 }
}

使用 DataRequestUploadRequest,你可以像之前(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

新的泛型 DownloadResponseDataResponse 很像,但包含了 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)

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。