alamofire-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAlamofire Patterns — Expert Decisions
Alamofire 模式——专家决策指南
Expert decision frameworks for Alamofire choices. Claude knows Alamofire syntax — this skill provides judgment calls for when Alamofire adds value and how to design interceptor chains.
面向Alamofire选择的专家决策框架。Claude熟悉Alamofire语法——本技能提供关于Alamofire何时能带来价值以及如何设计拦截器链的判断建议。
Decision Trees
决策树
Alamofire vs URLSession
Alamofire vs URLSession
What networking features do you need?
├─ Basic REST calls with JSON
│ └─ Modern URLSession is sufficient
│ async/await + Codable works well
│
├─ Complex authentication (token refresh, retry)
│ └─ Alamofire's RequestInterceptor shines
│ Built-in retry coordination
│
├─ Request/Response inspection and modification
│ └─ Does app need centralized logging/metrics?
│ ├─ YES → Alamofire EventMonitor
│ └─ NO → URLSession delegate suffices
│
├─ Certificate pinning
│ └─ Alamofire ServerTrustManager simplifies this
│ But URLSession can do it with delegates
│
└─ Multipart uploads with progress
└─ Alamofire upload API is cleaner
URLSession works but more boilerplateThe trap: Adding Alamofire for simple apps. If you just need basic GET/POST with JSON, URLSession's async/await API is clean enough and avoids a dependency.
你需要哪些网络功能?
├─ 基础REST请求(带JSON)
│ └─ 现代URLSession已足够
│ async/await + Codable表现出色
│
├─ 复杂认证(令牌刷新、重试)
│ └─ Alamofire的RequestInterceptor表现突出
│ 内置重试协调机制
│
├─ 请求/响应的检查与修改
│ └─ 应用是否需要集中式日志/指标?
│ ├─ 是 → 使用Alamofire EventMonitor
│ └─ 否 → URLSession代理已足够
│
├─ 证书固定
│ └─ Alamofire ServerTrustManager简化了此操作
│ 但URLSession也可通过代理实现
│
└─ 带进度的多部分上传
└─ Alamofire的上传API更简洁
URLSession也能实现但需要更多样板代码误区:为简单应用引入Alamofire。如果你只需要基础的GET/POST JSON请求,URLSession的async/await API已经足够简洁,且无需引入额外依赖。
Interceptor Chain Design
拦截器链设计
What cross-cutting concerns exist?
├─ Just auth token injection
│ └─ Single RequestAdapter
│
├─ Auth + retry on 401
│ └─ Authenticator pattern (Alamofire's built-in)
│ Handles refresh token race conditions
│
├─ Multiple concerns (auth, logging, caching headers)
│ └─ Compositor pattern
│ Interceptor(adapters: [...], retriers: [...])
│
└─ Request modification varies by endpoint
└─ Per-router interceptors
Different Session instances or conditional logic存在哪些横切关注点?
├─ 仅需注入认证令牌
│ └─ 单个RequestAdapter
│
├─ 认证 + 401时重试
│ └─ Authenticator模式(Alamofire内置)
│ 处理刷新令牌的竞态条件
│
├─ 多个关注点(认证、日志、缓存头)
│ └─ 组合器模式
│ Interceptor(adapters: [...], retriers: [...])
│
└─ 请求修改因端点而异
└─ 按路由器划分的拦截器
不同Session实例或条件逻辑Retry Strategy Selection
重试策略选择
What kind of failure?
├─ Auth failure (401)
│ └─ Refresh token and retry once
│ Use Authenticator, not generic retry
│
├─ Transient network error
│ └─ Is request idempotent?
│ ├─ YES → Retry with exponential backoff (3 attempts)
│ └─ NO → Don't retry (may cause duplicates)
│
├─ Server error (5xx)
│ └─ Retry for 503 (Service Unavailable) only
│ Other 5xx usually won't recover
│
└─ Client error (4xx except 401)
└─ Never retry
Request is malformed, retry won't help失败类型是什么?
├─ 认证失败(401)
│ └─ 刷新令牌并重试一次
│ 使用Authenticator,而非通用重试
│
├─ 临时网络错误
│ └─ 请求是否是幂等的?
│ ├─ 是 → 指数退避重试(3次)
│ └─ 否 → 不重试(可能导致重复操作)
│
├─ 服务器错误(5xx)
│ └─ 仅对503(服务不可用)重试
│ 其他5xx通常无法恢复
│
└─ 客户端错误(除401外的4xx)
└─ 绝不重试
请求格式错误,重试无济于事NEVER Do
绝对不要做的事
Interceptor Design
拦截器设计
NEVER refresh tokens in generic retry logic:
swift
// ❌ Race condition — multiple requests refresh simultaneously
final class BadInterceptor: RequestRetrier {
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
if response.statusCode == 401 {
Task {
try await refreshToken() // 5 requests = 5 refresh calls!
completion(.retry)
}
}
}
}
// ✅ Use Alamofire's Authenticator — coordinates refresh across requests
final class TokenAuthenticator: Authenticator {
func refresh(_ credential: OAuthCredential, for session: Session, completion: @escaping (Result<OAuthCredential, Error>) -> Void) {
// Single refresh, all waiting requests resume
}
}NEVER create new Session instances per request:
swift
// ❌ Loses connection pooling, memory inefficient
func fetchUser() async throws -> User {
let session = Session() // New session per call!
return try await session.request(endpoint).serializingDecodable(User.self).value
}
// ✅ Reuse session — connection pooling, shared interceptors
final class NetworkManager {
private let session: Session // Single instance
func fetchUser() async throws -> User {
try await session.request(endpoint).serializingDecodable(User.self).value
}
}NEVER use Interceptor for endpoint-specific logic:
swift
// ❌ Interceptor has complex conditionals
func adapt(_ urlRequest: URLRequest, ...) {
if urlRequest.url?.path.contains("/admin") {
// Add admin header
} else if urlRequest.url?.path.contains("/public") {
// Skip auth
}
}
// ✅ Use Router pattern — endpoint defines its own needs
enum APIRouter: URLRequestConvertible {
case adminEndpoint
case publicEndpoint
var requiresAuth: Bool {
switch self {
case .adminEndpoint: return true
case .publicEndpoint: return false
}
}
}绝对不要在通用重试逻辑中刷新令牌:
swift
// ❌ 竞态条件——多个请求同时刷新
final class BadInterceptor: RequestRetrier {
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
if response.statusCode == 401 {
Task {
try await refreshToken() // 5个请求会触发5次刷新调用!
completion(.retry)
}
}
}
}
// ✅ 使用Alamofire的Authenticator — 协调跨请求的刷新
final class TokenAuthenticator: Authenticator {
func refresh(_ credential: OAuthCredential, for session: Session, completion: @escaping (Result<OAuthCredential, Error>) -> Void) {
// 仅执行一次刷新,所有等待的请求会在刷新完成后恢复
}
}绝对不要为每个请求创建新的Session实例:
swift
// ❌ 失去连接池功能,内存效率低下
func fetchUser() async throws -> User {
let session = Session() // 每次调用都创建新Session!
return try await session.request(endpoint).serializingDecodable(User.self).value
}
// ✅ 复用Session — 连接池、共享拦截器
final class NetworkManager {
private let session: Session // 单例实例
func fetchUser() async throws -> User {
try await session.request(endpoint).serializingDecodable(User.self).value
}
}绝对不要使用拦截器处理端点特定逻辑:
swift
// ❌ 拦截器包含复杂条件判断
func adapt(_ urlRequest: URLRequest, ...) {
if urlRequest.url?.path.contains("/admin") {
// 添加管理员头部
} else if urlRequest.url?.path.contains("/public") {
// 跳过认证
}
}
// ✅ 使用Router模式 — 端点定义自身需求
enum APIRouter: URLRequestConvertible {
case adminEndpoint
case publicEndpoint
var requiresAuth: Bool {
switch self {
case .adminEndpoint: return true
case .publicEndpoint: return false
}
}
}Session Configuration
Session配置
NEVER disable SSL validation in production:
swift
// ❌ Security vulnerability
let manager = ServerTrustManager(evaluators: [
"api.production.com": DisabledTrustEvaluator() // MITM vulnerable!
])
// ✅ Use DisabledTrustEvaluator only for development
#if DEBUG
let evaluator = DisabledTrustEvaluator()
#else
let evaluator = DefaultTrustEvaluator()
#endifNEVER ignore response validation:
swift
// ❌ Silently accepts 4xx/5xx as success
session.request(endpoint)
.responseDecodable(of: User.self) { response in
// May decode error response as User!
}
// ✅ Always validate before decoding
session.request(endpoint)
.validate(statusCode: 200..<300)
.responseDecodable(of: User.self) { response in
// Only called for 2xx responses
}绝对不要在生产环境中禁用SSL验证:
swift
// ❌ 安全漏洞
let manager = ServerTrustManager(evaluators: [
"api.production.com": DisabledTrustEvaluator() // 易受中间人攻击!
])
// ✅ 仅在开发环境中使用DisabledTrustEvaluator
#if DEBUG
let evaluator = DisabledTrustEvaluator()
#else
let evaluator = DefaultTrustEvaluator()
#endif绝对不要忽略响应验证:
swift
// ❌ 静默接受4xx/5xx作为成功响应
session.request(endpoint)
.responseDecodable(of: User.self) { response in
// 可能会将错误响应解码为User!
}
// ✅ 始终先验证再解码
session.request(endpoint)
.validate(statusCode: 200..<300)
.responseDecodable(of: User.self) { response in
// 仅在2xx响应时调用
}Retry Logic
重试逻辑
NEVER retry non-idempotent requests:
swift
// ❌ May create duplicate orders
func placeOrder() {
session.request(APIRouter.createOrder)
.validate()
.response { response in
if response.error != nil {
self.placeOrder() // Retry — may duplicate!
}
}
}
// ✅ Use idempotency keys for non-idempotent operations
func placeOrder(idempotencyKey: String) {
session.request(APIRouter.createOrder(idempotencyKey: idempotencyKey))
// Server uses key to prevent duplicates
}NEVER retry immediately without backoff:
swift
// ❌ Hammers server during outage
let retryPolicy = RetryPolicy(retryLimit: 5) // Immediate retries
// ✅ Exponential backoff
let retryPolicy = RetryPolicy(
retryLimit: 3,
exponentialBackoffBase: 2,
exponentialBackoffScale: 0.5
)绝对不要重试非幂等请求:
swift
// ❌ 可能会创建重复订单
func placeOrder() {
session.request(APIRouter.createOrder)
.validate()
.response { response in
if response.error != nil {
self.placeOrder() // 重试——可能导致重复!
}
}
}
// ✅ 为非幂等操作使用幂等键
func placeOrder(idempotencyKey: String) {
session.request(APIRouter.createOrder(idempotencyKey: idempotencyKey))
// 服务器使用该键防止重复操作
}绝对不要无退避立即重试:
swift
// ❌ 服务中断时持续请求服务器
let retryPolicy = RetryPolicy(retryLimit: 5) // 立即重试
// ✅ 指数退避
let retryPolicy = RetryPolicy(
retryLimit: 3,
exponentialBackoffBase: 2,
exponentialBackoffScale: 0.5
)Essential Patterns
核心模式
Authenticator with Refresh Coordination
带刷新协调的Authenticator
swift
final class OAuthAuthenticator: Authenticator {
private let tokenStore: TokenStore
private let refreshService: RefreshService
func apply(_ credential: OAuthCredential, to urlRequest: inout URLRequest) {
urlRequest.headers.add(.authorization(bearerToken: credential.accessToken))
}
func refresh(_ credential: OAuthCredential, for session: Session, completion: @escaping (Result<OAuthCredential, Error>) -> Void) {
// Alamofire ensures only ONE refresh happens
// Other 401 requests wait for this to complete
refreshService.refresh(refreshToken: credential.refreshToken) { result in
switch result {
case .success(let tokens):
let newCredential = OAuthCredential(
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
expiration: tokens.expiration
)
self.tokenStore.save(newCredential)
completion(.success(newCredential))
case .failure(let error):
completion(.failure(error))
}
}
}
func didRequest(_ urlRequest: URLRequest, with response: HTTPURLResponse, failDueToAuthenticationError error: Error) -> Bool {
response.statusCode == 401
}
func isRequest(_ urlRequest: URLRequest, authenticatedWith credential: OAuthCredential) -> Bool {
urlRequest.headers["Authorization"] == "Bearer \(credential.accessToken)"
}
}swift
final class OAuthAuthenticator: Authenticator {
private let tokenStore: TokenStore
private let refreshService: RefreshService
func apply(_ credential: OAuthCredential, to urlRequest: inout URLRequest) {
urlRequest.headers.add(.authorization(bearerToken: credential.accessToken))
}
func refresh(_ credential: OAuthCredential, for session: Session, completion: @escaping (Result<OAuthCredential, Error>) -> Void) {
// Alamofire确保仅执行一次刷新
// 其他401请求会等待刷新完成
refreshService.refresh(refreshToken: credential.refreshToken) { result in
switch result {
case .success(let tokens):
let newCredential = OAuthCredential(
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
expiration: tokens.expiration
)
self.tokenStore.save(newCredential)
completion(.success(newCredential))
case .failure(let error):
completion(.failure(error))
}
}
}
func didRequest(_ urlRequest: URLRequest, with response: HTTPURLResponse, failDueToAuthenticationError error: Error) -> Bool {
response.statusCode == 401
}
func isRequest(_ urlRequest: URLRequest, authenticatedWith credential: OAuthCredential) -> Bool {
urlRequest.headers["Authorization"] == "Bearer \(credential.accessToken)"
}
}Compositor Interceptor
组合器拦截器
swift
// Combine multiple adapters and retriers
let interceptor = Interceptor(
adapters: [
AuthAdapter(tokenStore: tokenStore),
LoggingAdapter(),
DeviceInfoAdapter()
],
retriers: [
AuthRetrier(authenticator: authenticator),
NetworkRetrier(retryLimit: 3)
]
)
let session = Session(interceptor: interceptor)swift
// 组合多个适配器和重试器
let interceptor = Interceptor(
adapters: [
AuthAdapter(tokenStore: tokenStore),
LoggingAdapter(),
DeviceInfoAdapter()
],
retriers: [
AuthRetrier(authenticator: authenticator),
NetworkRetrier(retryLimit: 3)
]
)
let session = Session(interceptor: interceptor)Certificate Pinning
证书固定
swift
let evaluators: [String: ServerTrustEvaluating] = [
"api.yourapp.com": PinnedCertificatesTrustEvaluator(
certificates: Bundle.main.af.certificates,
acceptSelfSignedCertificates: false,
performDefaultValidation: true,
validateHost: true
)
]
let session = Session(
serverTrustManager: ServerTrustManager(evaluators: evaluators)
)swift
let evaluators: [String: ServerTrustEvaluating] = [
"api.yourapp.com": PinnedCertificatesTrustEvaluator(
certificates: Bundle.main.af.certificates,
acceptSelfSignedCertificates: false,
performDefaultValidation: true,
validateHost: true
)
]
let session = Session(
serverTrustManager: ServerTrustManager(evaluators: evaluators)
)Quick Reference
快速参考
When Alamofire Adds Value
Alamofire的适用场景
| Feature | URLSession | Alamofire |
|---|---|---|
| Basic REST | ✅ Sufficient | Overkill |
| Token refresh with retry | Tricky | ✅ Authenticator |
| Certificate pinning | Possible | ✅ Cleaner API |
| Request/Response logging | Custom | ✅ EventMonitor |
| Multipart upload progress | Verbose | ✅ Clean API |
| Connection pooling | Automatic | Automatic |
| 功能 | URLSession | Alamofire |
|---|---|---|
| 基础REST请求 | ✅ 已足够 | 冗余 |
| 带重试的令牌刷新 | 实现复杂 | ✅ Authenticator |
| 证书固定 | 可实现 | ✅ API更简洁 |
| 请求/响应日志 | 需自定义 | ✅ EventMonitor |
| 带进度的多部分上传 | 代码冗长 | ✅ API简洁 |
| 连接池 | 自动支持 | 自动支持 |
Interceptor Checklist
拦截器检查清单
- Single Session instance shared across app
- Authenticator for token refresh (not generic retry)
- Exponential backoff for transient failures
- Only retry idempotent requests
- Validate responses before decoding
- Certificate pinning for production
- 应用中共享单个Session实例
- 使用Authenticator进行令牌刷新(而非通用重试)
- 临时失败使用指数退避策略
- 仅重试幂等请求
- 解码前先验证响应
- 生产环境启用证书固定
Red Flags
危险信号
| Smell | Problem | Fix |
|---|---|---|
| New Session per request | Loses pooling | Share Session |
| DisabledTrustEvaluator in prod | Security hole | Proper pinning |
| Token refresh in RetryPolicy | Race condition | Use Authenticator |
| Retry without backoff | Server hammering | Exponential backoff |
| No .validate() call | Silent failures | Always validate |
| Complex conditionals in Interceptor | Wrong layer | Router pattern |
| 代码异味 | 问题 | 修复方案 |
|---|---|---|
| 每个请求创建新Session | 失去连接池功能 | 共享Session实例 |
| 生产环境使用DisabledTrustEvaluator | 安全漏洞 | 正确配置证书固定 |
| 在RetryPolicy中刷新令牌 | 竞态条件 | 使用Authenticator |
| 无退避立即重试 | 持续请求服务器 | 指数退避策略 |
| 未调用.validate() | 静默失败 | 始终验证响应 |
| 拦截器中包含复杂条件判断 | 层级错误 | 使用Router模式 |