首页
/ Alamofire 4.0 迁移指南:从 3.x 到全新请求体系与安全范式的完整升级路径

Alamofire 4.0 迁移指南:从 3.x 到全新请求体系与安全范式的完整升级路径

2026-09-05 20:12:52作者:滕妙奇

本文基于 Alamofire 官方仓库中的 Alamofire 4.0 Migration Guide 编写,系统梳理从 Alamofire 3.x 升级到 4.0 的全部破坏性变更(API 重命名、请求语法改写、URLConvertible/URLRequestConvertible 协议重构)、三大新功能(RequestAdapterRequestRetrier、Task Metrics)与五组升级特性(AFError 错误系统、ParameterEncoding 协议化、Request 子类化、响应校验与响应序列化器重构),并结合当前仓库源码逐一验证这些 v4 设计在代码中的落地形态,帮助维护旧版本代码的开发者完成平滑迁移,同时理解每个 API 变更背后的设计动机。

版本要求与升级收益

升级前提

根据迁移指南,Alamofire 4.0 的运行与开发环境要求如下:

项目 要求
iOS 8.0+
macOS 10.10.0+
tvOS 9.0+
watchOS 2.0+
Xcode 8.1+
Swift 3.0+

指南特别说明:如果需要在 macOS 10.9 上使用 Alamofire,应停留在支持 Swift 2.2/2.3 的最后一个 3.x 标签版本。作为对照,当前仓库根目录的 Package.swift 声明的平台已演进为 macOS 10.13 / iOS 12 / tvOS 12 / watchOS 4(swift-tools-version 6.3),说明 4.0 之后各主版本持续提升了对系统版本的底线要求——迁移时应以目标版本对应的 manifest 声明为准。

升级收益

指南将 4.0 的核心收益归纳为九点,可概括为「安全、可拦截、类型细化」三条主线:

  • 完整的 Swift 3 兼容性:全面遵循 Swift API Design Guidelines,绝大多数 API 随之改名改签名;
  • 全新错误系统:采用 AFError 类型,遵循 SE-0112(Swift Error 到 NSError 桥接)提案提出的模式;
  • RequestAdapter 协议:在每个 Request 实例化前检查并改写 URLRequest,例如统一注入 Authorization 头;
  • RequestRetrier 协议:检查失败的 Request 并决定重试,可构建 OAuth1/OAuth2/Basic Auth 等自定义认证体系;
  • ParameterEncoding 协议化:用协议取代 3.x 的枚举,失败时抛出错误而不是返回元组;
  • 新的 Request 类型族DataRequestDownloadRequestUploadRequestStreamRequest 各自携带专用进度、校验与序列化 API;
  • 新的进度 APIdownloadProgressuploadProgress 区分上传/下载进度,支持 ProgressInt64,回调队列可指定、默认 .main
  • 增强的响应校验:校验闭包能拿到 datatemporaryURL/destinationURL,可在闭包内解析服务端错误报文;
  • 新的下载目的地:可禁用文件移动、删除旧文件、创建中间目录;
  • 统一的新 Response 类型:下载响应暴露 temporaryURL/destinationURL,并在全新平台特性上暴露 Task Metrics。

破坏性 API 变更

4.0 完整采纳了 Swift 3 的新规范。指南无法穷举每一处改动,因此聚焦最常用 API 的对照改写。

命名空间变更

若干常用类型从 Manager/Request 的嵌套命名提升到全局命名空间,成为一等公民:

Alamofire 3.x Alamofire 4.x
Manager SessionManager
Request.TaskDelegate TaskDelegate
Request.DataTaskDelegate DataTaskDelegate
Request.DownloadTaskDelegate DownloadTaskDelegate
Request.UploadTaskDelegate UploadTaskDelegate

从当前仓库源码结构看,这一「文件与类型一一对应、按职责分目录」的组织方式被延续并进一步强化:核心类型分布在 Source/CoreRequestDataRequestDownloadRequestUploadRequestSessionSessionDelegate 等),功能扩展分布在 Source/Features(拦截器、编码器、校验、序列化、重试策略等),测试则按文件一一对应地放在 Tests 目录下。需要注意:在当前版本的源码中,SessionManager 已再次更名为 Session(见 Source/Core/Session.swift),迁移到 4.0 之后继续升级新版时仍需关注这类命名演进。

请求写法对照:Data / Download / Upload

Data Request——简单 URL 字符串

// 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 // method 默认 .get
    debugPrint(response)
}

3.x 时代回调拆成四个平铺参数,4.x 收敛为一个封装好的 response;HTTP 方法缺省为 .get,无需显式写出。

Data Request——带参数、进度与自定义校验的复杂请求

// 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: \(progress.fractionCompleted)")
    }
    .validate { request, response, data in
        // 自定义评估闭包现在包含 data(可解析数据、挖出错误信息)
        return .success
    }
    .responseJSON { response in
        debugPrint(response)
    }

关键差异有三处:参数类型从 [String: AnyObject] 变为新的 Parameters 类型别名;编码从枚举 .JSON 变为可配置的结构体 JSONEncoding.default;进度回调从裸字节数升级为 Progress 实例并可通过 queue: 指定调度队列,自定义校验闭包新增了 data 参数。

Download Request——简单场景

// 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 // method 默认 .get
    print(response.request)
    print(response.response)
    print(response.temporaryURL)
    print(response.destinationURL)
    print(response.error)
}

3.x 的痛点——下载完成后难以拿到最终文件 URL——在 4.x 中被 response.temporaryURLresponse.destinationURL 直接解决。

Download Request——使用 URLRequest 的场景

// Alamofire 3
let destination = DownloadRequest.suggestedDownloadDestination()

Alamofire.download(urlRequest, destination: destination).validate().responseData { response in
    debugPrint(response)
}

// Alamofire 4
Alamofire.download(urlRequest, to: destination).validate().responseData { response in
    debugPrint(response)
    print(response.temporaryURL)
    print(response.destinationURL)
}

Download Request——复杂场景:目的地闭包带文件操作选项

// 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
        // 自定义评估实现(拿不到临时/目的地 URL)
        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: \(progress.fractionCompleted)")
    }
    .validate { request, response, temporaryURL, destinationURL in
        // 自定义评估闭包现在包含文件 URL(可解析出错误信息)
        return .success
    }
    .responseJSON { response in
        debugPrint(response)
        print(response.temporaryURL)
        print(response.destinationURL)
    }

注意 4.x 中 DownloadFileDestination 闭包的返回类型从单一 URL 变为 (URL, DownloadOptions) 元组,文件操作选项随 URL 一起声明(详见后文「下载选项」一节)。

Upload Request——三种场景

// 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 // method 默认 .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("Upload Progress: \(progress.fractionCompleted)")
    }
    .downloadProgress { progress in // 默认在主队列调用
        print("Download Progress: \(progress.fractionCompleted)")
    }
    .validate { request, response, data in
        // 自定义评估闭包现在包含 data(可解析出错误信息)
        return .success
    }
    .responseJSON { response in
        debugPrint(response)
    }

3.x 中上传请求的 progress 到底是上传进度还是响应下载进度,语义含糊;4.x 通过 uploadProgressdownloadProgress 两个独立 API 彻底消除歧义。如指南所总结:虽然破坏性变更很多,但常用 API 依然保持了「一行代码发起复杂请求」的原始设计目标。

URLStringConvertible → URLConvertible

URLStringConvertible 有两处值得注意的变更。

协议重命名并改为可抛错方法。 3.x 的定义:

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

4.x 中更名为 URLConvertible,定义变为:

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

URLString 属性被彻底移除,替换为可抛错的 asURL() 方法。背景是:用户忘记对 URL 字符串做百分号转义时,Alamofire 3.x 会直接崩溃。团队过去坚持「URL 必须符合 RFC 2396」的立场,但更理想的行为是明确报告无效 URL 而非崩溃。之所以 3.x 做不到,根源就在于 URLStringConvertible 本身没有安全性——库无从得知如何智能修复一个非法字符串。4.x 之后,若无法从 URLConvertible 创建 URL,就会抛出 AFError.invalidURL 并在响应处理器中回传,让 Alamofire 可以安全地处理无效 URL。

当前仓库的 Source/Core/URLConvertible+URLRequestConvertible.swift 印证了这一设计的最终形态:String.asURL()URL(string:) 返回 nil 时抛出 AFError.invalidURL(url:)URL 直接返回自身;URLComponents.asURL()url 为 nil 时同样抛错。

URLRequest 不再遵循该协议。 旧版本里 URLRequest 遵循 URLStringConvertible 本来就是勉强的,还容易给多个 API 引入歧义。4.x 之后这段代码不再合法:

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

改为:

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

URLRequestConvertible:从属性到可抛错方法

URLRequestConvertible 在 3.x 中存在同样的安全隐患:

// Alamofire 3
public protocol URLRequestConvertible {
    var URLRequest: URLRequest { get }
}

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

URLRequest 属性被 asURLRequest() 方法取代,构造请求出错时可抛错。指南指出最可能受影响的是 Router 路由模式:3.x 的 Router 里不得不强解包不安全的数据或参数、把 ParameterEncoding 包在 do-catch 里;4.x 中改为实现 asURLRequest() 后,Router 内部遇到的任何错误都能被 Alamofire 自动捕获处理。当前仓库的 Source/Core/URLConvertible+URLRequestConvertible.swift 保留了这一形态,并且 URLRequest 自身遵循 URLRequestConvertibleasURLRequest() 直接返回 self),另提供 urlRequest 便捷计算属性(内部 try? asURLRequest())用于容忍失败的场景。

新功能

RequestAdapter:请求发出前的统一拦截点

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 的响应处理器。从当前仓库源码看,这一思想在后续版本中继续深化:Source/Features/RequestInterceptor.swift 中的 RequestAdapter 已改为基于 completion 的异步签名(adapt(_:for:completion:),以 Result<URLRequest, any Error> 回传结果),并新增了携带 RequestAdapterState(包含 requestID 与所属 Session)的重载;同时提供了闭包式 Adapter 类和可组合多个 adapter/retrier 的 Interceptor 类(adapter 链式执行、任一失败即终止;retrier 逐个尝试、任一触发重试即终止)。若项目仍在使用 4.0 时代的 throws 签名,升级到新版时需要把适配逻辑迁移到 completion 风格。

RequestRetrier:失败后的重试决策

RequestRetrier 是 4.0 的另一全新协议:

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 在(可选)延迟后被重试:

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

retrier 可以在 Request 完成、所有 Validation 闭包执行完之后检查它,再决定是否重试。指南强调:将 RequestAdapterRequestRetrier 组合使用,就能为 OAuth1、OAuth2、Basic Auth 搭建凭证刷新体系,甚至实现指数退避重试策略。当前仓库中这一机制同样持续演化(见 Source/Features/RequestInterceptor.swift):4.0 时代的 RequestRetryCompletion(shouldRetry, timeDelay) 二元组被更丰富的 RetryResult 枚举取代(.retry / .retryWithDelay(TimeInterval) / .doNotRetry / .doNotRetryWithError(Error),最后一种可以把重试过程中产生的新错误随原始错误一起回传),协议签名改为 retry(_:for:dueTo:completion:)。仓库中还内置了可直接使用的策略实现,如 Source/Features/RetryPolicy.swift,以及演示完整凭证刷新流程的 Source/Features/AuthenticationInterceptor.swift,对应的测试在 Tests/RetryPolicyTests.swiftTests/AuthenticationInterceptorTests.swift

Task Metrics:系统级网络统计

iOS/tvOS 10 与 macOS 10.12 引入了 URLSessionTaskMetrics API,封装了 Alamofire 自己无法计算的丰富请求/响应执行统计(API 形态与 Alamofire 的 Timeline 相似但信息量大得多)。4.0 将其直接挂在每个 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)
    }
}

对照当前仓库源码可以观察到 metrics 的演进轨迹:Source/Core/Response.swift 中的 DataResponse/DownloadResponse 均带有 metrics 属性,且 Source/Core/Request.swift 内部维护的是 metrics: [URLSessionTaskMetrics] 数组(多次重试会累积多条指标),对外通过 lastMetrics 暴露最近一条——这比 4.0 单值 metrics 的设计更贴合重试场景。

更新特性

全新错误系统:AFError

Alamofire 4 的错误系统遵循 SE-0112 提案的模式,核心是 AFError 这个 Error 枚举。指南列出的五个主 case 为:

  • .invalidURL(url: URLConvertible)——URLConvertible 无法创建有效 URL 时返回;
  • .parameterEncodingFailed(reason: ParameterEncodingFailureReason)——参数编码对象在编码过程中抛错;
  • .multipartEncodingFailed(reason: MultipartEncodingFailureReason)——多部分编码流程中某一步失败;
  • .responseValidationFailed(reason: ResponseValidationFailureReason)——validate() 调用失败;
  • .responseSerializationFailed(reason: ResponseSerializationFailureReason)——响应序列化器在序列化过程中出错。

每个 case 携带一个嵌套枚举形式的失败原因(reason),内含描述错误细节的附加信息,使得「错误从哪里来、该如何处理」在 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("Invalid URL: \(url) - \(error.localizedDescription)")
        case .parameterEncodingFailed(let reason):
            print("Parameter encoding failed: \(error.localizedDescription)")
            print("Failure Reason: \(reason)")
        case .multipartEncodingFailed(let reason):
            print("Multipart encoding failed: \(error.localizedDescription)")
            print("Failure Reason: \(reason)")
        case .responseValidationFailed(let reason):
            print("Response validation failed: \(error.localizedDescription)")
            print("Failure Reason: \(reason)")

            switch reason {
            case .dataFileNil, .dataFileReadFailed:
                print("Downloaded file could not be read")
            case .missingContentType(let acceptableContentTypes):
                print("Content Type Missing: \(acceptableContentTypes)")
            case .unacceptableContentType(let acceptableContentTypes, let responseContentType):
                print("Response content type: \(responseContentType) was unacceptable: \(acceptableContentTypes)")
            case .unacceptableStatusCode(let code):
                print("Response status code was unacceptable: \(code)")
            }
        case .responseSerializationFailed(let reason):
            print("Response serialization failed: \(error.localizedDescription)")
            print("Failure Reason: \(reason)")
        }

        print("Underlying error: \(error.underlyingError)")
    } else if let error = error as? URLError {
        print("URLError occurred: \(error)")
    } else {
        print("Unknown error: \(error)")
    }
}

这套设计允许按需下钻到任意深度定位错误,也把开发者从「到处处理 NSError」的负担中解放出来。由于不再需要为 NSError 桥接保留第二个泛型参数,ResultResponse 泛型类型得以简化为单参数,响应序列化逻辑随之简化。

当前仓库的 Source/Core/AFError.swift 验证了五个 v4 主 case 全部保留(.invalidURL.parameterEncodingFailed.multipartEncodingFailed.responseValidationFailed.responseSerializationFailed),并在其基础上扩展了后续版本引入的 case:.parameterEncoderFailed(对应新增的 ParameterEncoder 体系)、.requestAdaptationFailed(adapter 抛错,正是 v4 adapter 机制的产物)、.requestRetryFailed.serverTrustEvaluationFailed.sessionDeinitialized.sessionInvalidated.sessionTaskFailed.urlRequestValidationFailed 等。此外源码提供了 isResponseValidationError 等布尔便捷属性与 underlyingError 聚合访问器(见 Source/Core/AFError.swift),错误断言行为的单测可参考 Tests/AFError+AlamofireTests.swift。

ParameterEncoding 协议:告别枚举

3.x 的 ParameterEncoding 枚举服务了两年以上,但存在四个公认痛点:

  • .url case 总是让人困惑——它按 HTTP 方法决定参数落点;
  • .urlEncodedInURL case 只是为绕开 .url 的行为而存在的「眼部异物」;
  • .JSON.PropertyList 无法接受格式化/写入选项;
  • .custom case 对用户而言难以掌握。

因此 4.0 彻底移除了枚举。ParameterEncoding 变为协议,由 URLEncodingJSONEncodingPropertyListEncoding 三个具体结构体支撑,并引入新的 Parameters 类型别名:

public typealias Parameters = [String: Any]

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

当前仓库 Source/Core/ParameterEncoding.swift 中该协议形态保持不变,仅因 Swift 并发演进将 Parameters 收紧为 [String: any Any & Sendable]、协议加入 Sendable 约束——迁移时注意参数字典中的值需满足 Sendable 要求。

URL Encoding

新的 URLEncoding 结构体包含一个 Destination 枚举,支持三种参数落点:

  • .methodDependent——对 GETHEADDELETE 请求把编码后的查询串追加到已有 query string,对其他 HTTP 方法则设为 HTTP body;
  • .queryString——把编码结果设为或追加到已有 query string;
  • .httpBody——把编码结果设为 URL 请求的 HTTP body。

请求创建在参数编码方面保持与之前相同的签名与默认行为:

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

Alamofire.request(urlString, parameters: parameters) // 默认 => URLEncoding(destination: .methodDependent)
Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding(destination: .queryString))
Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding(destination: .httpBody))

// 静态便捷属性(官方更推荐这种更简洁的写法)
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

JSONEncoding 暴露了对 JSON 写入选项的定制能力:

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

Alamofire.request(urlString, parameters: parameters, encoding: JSONEncoding(options: []))
Alamofire.request(urlString, parameters: parameters, encoding: JSONEncoding(options: .prettyPrinted))

// 静态便捷属性
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))

// 静态便捷属性
Alamofire.request(urlString, parameters: parameters, encoding: PropertyListEncoding.xml)
Alamofire.request(urlString, parameters: parameters, encoding: PropertyListEncoding.binary)

自定义编码

创建自定义 ParameterEncoding 现在只需实现协议;更多示例可参考仓库 READMETests/ParameterEncodingTests.swift

Request 子类化:按类型拆分 API

4.0 中 requestdownloaduploadstream 不再返回泛化的 Request,而是返回具体的 Request 子类型。动机来自两个社区长期困惑的问题:

  • 进度:上传请求上 progress 报告的是什么?上传进度?响应下载进度?如果两者都报告,何时切换?
  • 响应序列化器:序列化器本是为 data 和 upload 请求设计的。下载完成后怎么拿 fileURL?responseData/responseString/responseJSON 链在 download 或 stream 请求上到底做什么?

4.0 因此定义了四个子类,各自承载定制化的链式 API:

open class Request {
    // 公共属性、认证与状态方法,以及
    // CustomStringConvertible / CustomDebugStringConvertible 一致性
}

open class DataRequest: Request {
    // 流(注意与 StreamRequest 不同)与下载进度方法
}

open class DownloadRequest: Request {
    // 下载目的地与选项、resume data、下载进度方法
}

open class UploadRequest: DataRequest {
    // 继承全部 DataRequest API,另含上传进度方法
}

open class StreamRequest: Request {
    // 仅继承 Request API,目前无其他定制 API
}

当前仓库源码证实了该层级结构被完整保留:RequestSource/Core/Request.swift)→ DataRequest: RequestSource/Core/DataRequest.swift)→ UploadRequestSource/Core/UploadRequest.swift)继承自 DataRequestDownloadRequestSource/Core/DownloadRequest.swift)与 StreamRequest 直接继承 Request,各类型的扩展 API 分布在同名文件中,文档站对应页面见 docs/Classes/DataRequest.htmldocs/Classes/DownloadRequest.html 等。

下载与上传进度

data、download、upload 请求的进度报告系统被彻底重新设计:每种请求类型都提供返回底层 Progress 实例的进度 API,闭包在指定队列上调用,默认主队列。

Data Request 进度

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

Download Request 进度

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

Upload Request 进度

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

至此,上传请求上区分上传/下载进度不再有任何歧义。

下载文件目的地(Destination)

3.x 中成功的下载请求总会把临时文件移动到 destination 闭包给出的最终 URL。这个「便利」带来两个限制:

  • 强制性:即使你的场景并不需要移动文件,API 也强迫你提供 destination 闭包;
  • 限制性:移动前无法调整文件系统——比如需要在移动前删除目的地已存在的文件,或为目的地 URL 创建中间目录。

4.0 的第一项增强是目的地闭包变为可选:默认 destinationnil,文件不会被移动,直接返回临时 URL:

Alamofire.download(urlString).responseData { response in
    print("Temporary URL: \(response.temporaryURL)")
}

下载选项(DownloadOptions)

第二项重大变更是为 destination 闭包增加文件操作选项。为此创建了 DownloadOptions 类型并加入 DownloadFileDestination 闭包签名:

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

4.0 支持的两个选项:

  • .createIntermediateDirectories——为目的地 URL 创建中间目录;
  • .removePreviousFile——删除目的地 URL 处已存在的旧文件。
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
    return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}

Alamofire.download(urlString, to: destination).response { response in
    debugPrint(response)
}

文件操作若出错,DownloadResponse 上的 error 类型为 URLError。当前仓库中这两个选项保留为 DownloadRequest.OptionsOptionSetcreateIntermediateDirectories = 1 << 0removePreviousFile = 1 << 1,见 Source/Core/DownloadRequest.swift),命名空间从顶层 DownloadOptions 收敛到 DownloadRequest 之下,行为语义未变;下载与目的地相关行为的测试覆盖见 Tests/DownloadTests.swift

响应校验:把数据直接交给闭包

4.0 的响应校验系统在两个方面做了改进:向校验闭包暴露底层 data;借助 Request 子类化,为不同请求类型定制校验闭包签名,下载请求可暴露 temporaryURLdestinationURL

DataRequest 的校验闭包

DataRequest 上(由 UploadRequest 继承)的 Validation 闭包变为:

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

由于 Data? 直接暴露在闭包参数中,不再需要写 Request 扩展去间接访问。典型用法:

Alamofire.request(urlString)
    .validate { request, response, data in
        guard let data = data else { return .failure(customError) }

        // 1) 校验响应,确认一切正常
        // 2) 若校验失败,现在可以从 data 中解析出错误信息,
        //    按需附加到自定义错误中

        return .success
    }
    .response { response in
        debugPrint(response)
    }

DownloadRequest 的校验闭包

DownloadRequestValidation 闭包类似,但更贴合下载场景:

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

temporaryURLdestinationURL 让你能直接在行内闭包中访问服务端返回的数据,例如在创建自定义错误前检查文件内容:

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

指南进一步指出:由于底层服务端数据被直接暴露给行内闭包,你可以解析其中嵌入的错误信息来构造包含服务端错误消息的自定义错误;如果载荷 schema 与响应序列化器闭包中一致,还可以直接调用响应序列化器来提取错误消息,避免重复逻辑。

响应序列化器:从平铺参数到封装的 Response 类型

3.x 的响应序列化系统存在几个严重的限制:

  • 序列化 API 可以链到 download 和 stream 请求上,但结果是未定义行为——下载完成怎么拿 fileURL?responseData/responseString/responseJSON 链在 download 或 stream 请求上到底做什么?
  • response API 返回 4 个平铺参数而非封装的 Response 类型——任何签名变更都无法向后兼容,且在序列化/非序列化 API 之间切换时容易造成难以调试的编译错误。

4.0 先把 Request 拆分子类,再为特定请求类型创建定制的响应序列化 API。迁移前需先理解四类新的 Response 类型。

DefaultDataResponse:未序列化的数据响应

DefaultDataResponse 表示未经序列化的服务端响应——没有 Alamofire 处理,只是从 SessionDelegate API 收集全部响应信息并封装为简单结构体:

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 API 返回的类型:

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

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

DataResponse:泛型序列化响应

泛型 DataResponse 相当于 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 上保留了与之前相同的响应序列化 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)
}

DefaultDownloadResponse:未序列化的下载响应

由于下载的工作方式与 data/upload 不同,4.0 提供了贴合其行为的定制下载 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 }
}

由新的 DownloadRequest.response API 返回:

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

DownloadResponse:泛型下载序列化响应

DownloadResponse 与泛型 DataResponse 类似,但承载下载请求的信息。它由 DownloadRequest 上新增的四个 API 返回,这四个 API 与 DataRequest 的对应 API 同名同功能,只是数据从底层临时或目的地 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 让「下载文件 + 序列化响应」可以在单次调用链中完成。

自定义响应序列化器

如果你自己写过自定义响应序列化器,指南建议参考 Alamofire 自身在 data 与 download 请求间共享实现的做法:把序列化逻辑下沉到 Request 基类,再在两个子类型上分别暴露类型正确的 API,从而避免逻辑重复。当前仓库中这一分层依然清晰可见——序列化器协议(DataResponseSerializerProtocolDownloadResponseSerializerProtocol)与默认实现集中定义在 Source/Features/ResponseSerialization.swiftDownloadRequestresponseData/responseString/responseJSON/responsePropertyList 内部正是复用 data 侧的序列化逻辑并从文件 URL 加载数据;行为验证可参考 Tests/ResponseSerializationTests.swift

迁移落地时的源码对照与版本差异提示

本文以 4.0 迁移指南为主体;当前仓库为后续主版本,源码在继承 4.0 架构的基础上继续演化。对照源码做迁移与后续升级时,建议把握以下要点:

  1. 4.0 五大 AFError 主 case 全部保留Source/Core/AFError.swift),并按版本增量扩展了 adapter/retrier、会话生命周期、服务端信任评估等新 case——按 v4 模式编写的错误处理代码在新版中依然成立;
  2. URLConvertible/URLRequestConvertible 的可抛错方法形态未变(Source/Core/URLConvertible+URLRequestConvertible.swift),Router 中实现 asURLRequest() 的 v4 迁移成果可直接沿用;
  3. Adapter/Retrier 由 throws 签名改为 completion 签名,并升级为 RequestInterceptor 统一抽象(Source/Features/RequestInterceptor.swift),重试决策从 (Bool, TimeInterval) 升级为 RetryResult 四态枚举;
  4. SessionManager 已更名为 SessionDownloadOptions 收敛为 DownloadRequest.OptionsResponse 类型引入 Success/Failure 双泛型(Source/Core/Response.swift),metrics 支持重试累积的多条记录——这些是 4.0 之后的增量变更,迁移到 4.0 后再升级新版时需逐一核对;
  5. Request 子类层级(DataRequestUploadRequestDownloadRequest/StreamRequest 平行于 DataRequest)与文档描述一致,进度、目的地、校验、序列化四类 API 按类型归属的划分延续至今。

完整的 API 对照文档、各特性说明与可运行示例,可在仓库内继续查阅:Documentation/Usage.md(当前版本用法)、Documentation/AdvancedUsage.md(高级特性)、CHANGELOG.md(版本变更轨迹),以及 Tests 目录下与每类 API 一一对应的测试文件。

迁移检查清单

  • [ ] 全局替换 ManagerSessionManagerRequest.XxxTaskDelegate → 顶层 XxxTaskDelegate(后续版本再注意 SessionManagerSession);
  • [ ] 所有 Alamofire.request/download/upload 调用按 v4 签名改写(默认方法、Parameters 类型、to:/with: 标签);
  • [ ] 删除 urlString 属性访问,改用 url?.absoluteString
  • [ ] 自定义类型:URLStringConvertible 改为实现 asURL() throws -> URL;Router 改为实现 asURLRequest() throws -> URLRequest
  • [ ] 参数编码:枚举 .JSON/.url/.urlEncodedInURL 等替换为 JSONEncoding/URLEncoding(destination:) 等结构体;
  • [ ] 进度回调统一为 Progress 实例,并确认 queue: 参数指定的调度队列符合预期;
  • [ ] 错误处理:NSError 分支改写为对 AFErrorswitch,利用 reason 嵌套枚举下钻;
  • [ ] 下载:确认是否真的需要移动文件(不需要则省略 to: 参数),需要移动则在闭包中返回 (URL, DownloadOptions) 并按需使用 .removePreviousFile/.createIntermediateDirectories
  • [ ] 校验闭包按请求类型使用新签名(Data?temporaryURL/destinationURL);
  • [ ] 序列化 API 仅在对应请求类型上调用(data/upload 用 DataResponse,download 用 DownloadResponse),Task Metrics 访问处加可用性检查。
登录后查看全文
热门项目推荐
相关项目推荐