Alamofire: Uploads, Interceptors & Sessions
Uploads & Downloads
// Multipart form upload — files plus regular fields, with progress
AF.upload(
multipartFormData: { formData in
formData.append(imageData, withName: "avatar", fileName: "avatar.jpg", mimeType: "image/jpeg")
formData.append("Alice".data(using: .utf8)!, withName: "name")
},
to: "https://api.example.com/profile"
)
.uploadProgress { progress in
print("Upload: \(progress.fractionCompleted * 100)%")
}
.validate()
.responseDecodable(of: Profile.self) { response in }
// Downloading straight to disk (large files) instead of buffering in memory
let destination: DownloadRequest.Destination = { _, _ in
let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
.appendingPathComponent("report.pdf")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
AF.download("https://api.example.com/report.pdf", to: destination)
.downloadProgress { progress in print(progress.fractionCompleted) }
.response { response in
if let fileURL = response.fileURL { print("Saved to \(fileURL)") }
}Auth Token Refresh with RequestInterceptor
// A RequestInterceptor = a RequestAdapter (attach headers) +
// a RequestRetrier (decide whether/how to retry a failed request)
final class AuthInterceptor: RequestInterceptor {
private var tokenProvider: TokenProvider
init(tokenProvider: TokenProvider) {
self.tokenProvider = tokenProvider
}
func adapt(_ urlRequest: URLRequest, for session: Session,
completion: @escaping (Result<URLRequest, Error>) -> Void) {
var request = urlRequest
request.setValue("Bearer \(tokenProvider.accessToken)", forHTTPHeaderField: "Authorization")
completion(.success(request))
}
func retry(_ request: Request, for session: Session, dueTo error: Error,
completion: @escaping (RetryResult) -> Void) {
guard request.response?.statusCode == 401 else {
completion(.doNotRetry)
return
}
tokenProvider.refreshToken { success in
completion(success ? .retry : .doNotRetryWithError(error))
}
}
}
let session = Session(interceptor: AuthInterceptor(tokenProvider: tokenProvider))
session.request("https://api.example.com/me").responseDecodable(of: User.self) { response in }Custom Session Configuration
// AF is just Session.default — create your own for scoped configuration
let configuration = URLSessionConfiguration.af.default
configuration.timeoutIntervalForRequest = 15
// Certificate pinning for sensitive endpoints
let trustManager = ServerTrustManager(evaluators: [
"api.example.com": PinnedCertificatesTrustEvaluator()
])
let apiSession = Session(
configuration: configuration,
interceptor: AuthInterceptor(tokenProvider: tokenProvider),
serverTrustManager: trustManager
)
// EventMonitor — observe every request on this Session, e.g. for debug logging
final class LoggingMonitor: EventMonitor {
func requestDidResume(_ request: Request) {
print("→ \(request)")
}
}
let debugSession = Session(eventMonitors: [LoggingMonitor()])When to Reach for Alamofire vs Plain URLSession
Alamofire builds on URLSession — it doesn't replace it, and caching/TLS still follow URLSession's underlying behavior.
Modern URLSession with async/await (`URLSession.shared.data(for:)`) covers a lot of what originally motivated Alamofire, so simple apps may not need the dependency at all.
Alamofire earns its keep for multipart uploads, download progress, RequestInterceptor-based auth/retry, and certificate pinning — all noticeably more boilerplate to hand-roll with raw URLSession.
Response handlers default to callback on the main queue, unlike raw URLSession completion handlers which always run on a background queue.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free