alamofire-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Alamofire 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 boilerplate
The 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()
#endif
NEVER 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的适用场景

FeatureURLSessionAlamofire
Basic REST✅ SufficientOverkill
Token refresh with retryTricky✅ Authenticator
Certificate pinningPossible✅ Cleaner API
Request/Response loggingCustom✅ EventMonitor
Multipart upload progressVerbose✅ Clean API
Connection poolingAutomaticAutomatic
功能URLSessionAlamofire
基础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

危险信号

SmellProblemFix
New Session per requestLoses poolingShare Session
DisabledTrustEvaluator in prodSecurity holeProper pinning
Token refresh in RetryPolicyRace conditionUse Authenticator
Retry without backoffServer hammeringExponential backoff
No .validate() callSilent failuresAlways validate
Complex conditionals in InterceptorWrong layerRouter pattern
代码异味问题修复方案
每个请求创建新Session失去连接池功能共享Session实例
生产环境使用DisabledTrustEvaluator安全漏洞正确配置证书固定
在RetryPolicy中刷新令牌竞态条件使用Authenticator
无退避立即重试持续请求服务器指数退避策略
未调用.validate()静默失败始终验证响应
拦截器中包含复杂条件判断层级错误使用Router模式