【Alamofire】优雅的 Swift 网络库——告别繁琐的 URLSession
iOS三方库精读 · 第 1 期
一、一句话介绍
Alamofire 是一个用于 iOS / macOS / watchOS / tvOS 的 Swift HTTP 网络库,它让发起网络请求、处理响应、上传/下载文件变得声明式、可组合、且极易阅读。
| 属性 | 信息 |
|---|---|
| ⭐ GitHub Stars | ~41k |
| 最新版本 | 5.x(当前 5.9+) |
| License | MIT |
| 支持平台 | iOS 10+ / macOS 10.12+ / tvOS 10+ / watchOS 3+ |
| Swift 最低版本 | Swift 5.7+ |
二、为什么选择它
原生 URLSession 的痛点
苹果的 URLSession 功能完整,但在工程实践中会遇到这些问题:
| 原生 URLSession | Alamofire |
|---|---|
| 需要手动构建 URLRequest | 链式 API,一行发起请求 |
| 响应解析需要大量样板代码 | 内建 Decodable 自动解析 |
| 上传/下载进度管理繁琐 | 原生支持进度回调 |
| 拦截器/重试需要自行实现 | 内建 RequestInterceptor |
| 错误处理分散、不统一 | 统一的 AFError 体系 |
核心优势:
- 声明式链式调用,代码意图一目了然
- 内建 JSON/Decodable 解析,减少胶水代码
- RequestInterceptor:拦截、重试、Token 刷新统一处理
- EventMonitor:全链路可观测,调试/日志非常方便
- async/await 原生支持(5.5 起)
三、核心功能速览
基础层(新手必读)
集成方式(SPM 推荐)
在 Package.swift 或 Xcode 的 Package Dependencies 中添加:
https://github.com/Alamofire/Alamofire.git
最简单的 GET 请求
// Swift 5.7+
import Alamofire
AF.request("https://httpbin.org/get").responseJSON { response in
print(response.value ?? "No data")
}
使用 async/await(推荐)
let response = await AF.request("https://httpbin.org/get")
.serializingDecodable(MyModel.self)
.response
switch response.result {
case .success(let model): print(model)
case .failure(let error): print(error)
}
进阶层(最佳实践)
带参数的 POST 请求
let parameters: [String: Any] = ["username": "swift", "password": "123456"]
AF.request(
"https://httpbin.org/post",
method: .post,
parameters: parameters,
encoding: JSONEncoding.default,
headers: ["Authorization": "Bearer your_token"]
)
.validate(statusCode: 200..<300) // 自动校验状态码
.responseDecodable(of: LoginResponse.self) { response in
// 直接拿到强类型 Model
}
文件上传(带进度)
AF.upload(
multipartFormData: { form in
form.append(fileData, withName: "file", fileName: "photo.jpg", mimeType: "image/jpeg")
},
to: "https://example.com/upload"
)
.uploadProgress { progress in
print("上传进度:\(progress.fractionCompleted)")
}
.responseDecodable(of: UploadResult.self) { response in
print(response.value)
}
文件下载
let destination = DownloadRequest.suggestedDownloadDestination()
AF.download("https://example.com/file.zip", to: destination)
.downloadProgress { progress in
print("下载进度:\(Int(progress.fractionCompleted * 100))%")
}
.responseURL { response in
print("保存路径:\(response.fileURL)")
}
深入层(源码视角)
Alamofire 5 的核心模块职责:
| 模块 | 职责 |
|---|---|
Session | 对 URLSession 的封装,全局入口(AF 是默认单例) |
Request 体系 | DataRequest / UploadRequest / DownloadRequest 三条请求链路 |
RequestInterceptor | adapt 修改请求 + retry 重试逻辑分离 |
ResponseSerializer | 将 Data 转换为目标类型,可自定义扩展 |
EventMonitor | 全链路事件监听,用于日志/埋点 |
四、实战演示
场景:带 Token 自动刷新的 API 客户端
这是工程中最常见的场景——Token 过期后自动刷新并重试原始请求。
// Swift 5.7+
// 1. 定义拦截器
final class AuthInterceptor: RequestInterceptor {
private var accessToken: String = KeychainHelper.accessToken
private var isRefreshing = false
private var requestsToRetry: [RetryCompletion] = []
// adapt:每次请求前注入 Token
func adapt(_ urlRequest: URLRequest,
for session: Session,
completion: @escaping (Result<URLRequest, Error>) -> Void) {
var request = urlRequest
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
completion(.success(request))
}
// retry:401 时触发刷新
func retry(_ request: Request,
for session: Session,
dueTo error: Error,
completion: @escaping RetryCompletion) {
guard let response = request.task?.response as? HTTPURLResponse,
response.statusCode == 401 else {
completion(.doNotRetry)
return
}
requestsToRetry.append(completion)
guard !isRefreshing else { return }
refreshToken { [weak self] success in
self?.requestsToRetry.forEach { $0(success ? .retry : .doNotRetry) }
self?.requestsToRetry.removeAll()
}
}
private func refreshToken(completion: @escaping (Bool) -> Void) {
isRefreshing = true
AF.request("https://api.example.com/refresh",
method: .post,
parameters: ["refreshToken": KeychainHelper.refreshToken])
.responseDecodable(of: TokenResponse.self) { [weak self] response in
self?.isRefreshing = false
if let token = response.value?.accessToken {
self?.accessToken = token
KeychainHelper.accessToken = token
completion(true)
} else {
completion(false)
}
}
}
}
// 2. 创建自定义 Session(全局单例,推荐)
enum APIClient {
static let session = Session(interceptor: AuthInterceptor())
static func fetchUserProfile() async throws -> UserProfile {
try await session.request("https://api.example.com/profile")
.validate()
.serializingDecodable(UserProfile.self)
.value // throws on error
}
}
// 3. 调用
Task {
do {
let profile = try await APIClient.fetchUserProfile()
print("用户:\(profile.name)")
} catch {
print("请求失败:\(error)")
}
}
这个示例涵盖了:Token 注入、自动刷新、队列等待、async/await 调用——工程级最常见的模式。
五、源码亮点
进阶层:值得借鉴的用法
链式调用设计
Alamofire 所有方法都返回 Self(请求对象本身),使得可以无限链式组合:
AF.request(url)
.validate() // 校验
.responseDecodable(of: T.self) // 解析
.uploadProgress { } // 进度
// 每一步都是独立关注点,互不干扰
自定义 ResponseSerializer
// 扩展支持自定义格式(如 protobuf)
struct ProtobufSerializer<T: Message>: ResponseSerializer {
func serialize(request: URLRequest?, response: HTTPURLResponse?,
data: Data?, error: Error?) throws -> T {
guard let data = data else { throw AFError.responseSerializationFailed(...) }
return try T(serializedData: data)
}
}
深入层:设计思想解析
责任链模式(Chain of Responsibility)
RequestInterceptor 的 adapt → retry 两个钩子将「请求构造」与「失败重试」完全分离,任何一个环节都可以独立替换,不影响其他逻辑。这是典型的责任链 + 开闭原则实践。
EventMonitor:观察者模式的正确姿势
// 实现一个打印所有请求的 Logger
final class NetworkLogger: EventMonitor {
func requestDidFinish(_ request: Request) {
print("✅ \(request.request?.url?.absoluteString ?? "")")
}
func request<Value>(_ request: DataRequest,
didParseResponse response: DataResponse<Value, AFError>) {
print("📦 StatusCode: \(response.response?.statusCode ?? 0)")
}
}
// 注入 Session
let session = Session(eventMonitors: [NetworkLogger()])
不侵入业务代码,零耦合实现全链路可观测——比 print 打散在各处优雅得多。
六、踩坑记录
问题 1:responseJSON 废弃警告
- 原因:5.5+ 起
responseJSON被标记为 deprecated,官方推荐responseDecodable - 解决:定义
DecodableModel,使用.responseDecodable(of: MyModel.self)
问题 2:多个请求并发刷新 Token 导致死循环
- 原因:多个请求同时 401,每个都触发了刷新逻辑
- 解决:拦截器中用
isRefreshingflag + 队列缓存等待回调(见上方实战示例)
问题 3:AF.request 在 Background Task 中失效
- 原因:默认
Session使用前台 URLSession 配置 - 解决:自建 Session 并传入
URLSessionConfiguration.background(withIdentifier:)
let config = URLSessionConfiguration.background(withIdentifier: "com.app.bg")
let bgSession = Session(configuration: config)
问题 4:上传大文件内存暴涨
- 原因:使用
Data形式上传会将整个文件加载进内存 - 解决:使用
fileURL形式上传,Alamofire 会以流式方式读取
AF.upload(fileURL, to: "https://example.com/upload")
问题 5:.validate() 没有按预期触发
- 原因:没有加
.validate(),Alamofire 默认不对 4xx/5xx 报错 - 解决:养成习惯,链式调用中永远加
.validate(statusCode: 200..<300)
问题 6:响应在主线程,但 UI 更新闪烁
- 原因:
responseDecodable默认回调在主队列,但复杂解析会短暂阻塞 - 解决:用
queue:参数将解析切到后台,主动 dispatch 到主线程更新 UI
AF.request(url).responseDecodable(of: T.self, queue: .global(qos: .userInitiated)) { response in
DispatchQueue.main.async { /* 更新 UI */ }
}
七、延伸思考
同类库横向对比
| 库 | 语言 | 特点 | 学习曲线 | 维护状态 |
|---|---|---|---|---|
| Alamofire | Swift | 功能全面,生态最成熟 | 中 | 活跃 |
| Moya | Swift | 基于 Alamofire,API 抽象层 | 中高 | 活跃 |
| URLSession + async/await | Swift | 零依赖,苹果原生 | 低(但样板多) | 官方 |
| AFNetworking | Objective-C | OC 项目首选 | 低 | 维护模式 |
推荐使用场景
- ✅ 中大型 Swift 项目,需要统一网络层
- ✅ 需要 Token 刷新、请求重试等复杂拦截逻辑
- ✅ 需要完善的上传/下载进度管理
- ✅ 团队协作,希望网络层有统一规范
不推荐场景
- ❌ 简单脚本或极小型项目(引入成本 > 收益)
- ❌ 纯 SwiftUI + async/await 项目,原生 URLSession 已经足够
- ❌ 对包体积极度敏感的场景
八、参考资源
- Alamofire GitHub
- 官方文档 Usage.md
- Advanced Usage
- WWDC 2021 Use async/await with URLSession(了解苹果原生对标方案)
- 系列 Demo 仓库(持续更新):
github.com/yourname/ios-lib-demos
九、本期互动
小作业
基于本文的 AuthInterceptor 示例,扩展实现以下功能:当 Token 刷新失败(服务端返回 400)时,自动跳转到登录页,并取消所有等待中的请求。在评论区贴出你的关键代码实现。
思考题
Alamofire 的 RequestInterceptor 将「修改请求」和「重试决策」放在同一个对象里——你认为这是合理的设计吗?如果让你重新设计这个接口,你会如何拆分职责?
读者征集
下一期预计介绍 Kingfisher(图片加载库),如果你在使用 Kingfisher 时踩过坑,欢迎评论区留言,优质踩坑经历将收录进下一期《踩坑记录》章节!
📅 本系列每周五晚更新 ➡️ 第1期:Alamofire · ○ 第2期:Kingfisher · ○ 第3期:待定 · ○ 第4期:待定