Alamofire
Most popular third-party HTTP networking library for Swift. Built on URLSession while providing more user-friendly and elegant API. Chainable requests, automatic retry, response validation, JSON serialization, and Swift Concurrency support.
GitHub Overview
Alamofire/Alamofire
Elegant HTTP Networking in Swift
Topics
Star History
Library
Alamofire
Overview
Alamofire is a high-featured HTTP networking library for Swift developed under the concept of "Elegant HTTP Networking in Swift." As the de facto standard library in iOS and macOS development with overwhelming adoption rates, it features a design that wraps complex URLSession processing with simple and intuitive APIs. Providing comprehensive features that meet enterprise-level HTTP communication requirements including chainable methods, automatic JSON conversion, authentication systems, SSL certificate pinning, and progress monitoring, it significantly contributes to Swift developers' productivity improvements.
Details
Alamofire 2025 edition continues to evolve as the definitive HTTP library for modern iOS and macOS development with the latest Swift 6.0 support and complete Swift Concurrency support. Boasting mature APIs and exceptional stability through over 10 years of development experience, it supports a wide range of platforms from iOS 8.0+ to iOS 18.0+. In addition to a robust URLSession-based architecture, it integrates modern technologies such as chainable syntax, type-safe Codable support, automatic retry functionality, and experimental WebSocket support. Compared to Apple's standard URLSession, it achieves significant development efficiency improvements and enables flexible adoption through Swift Package Manager, CocoaPods, and Carthage.
Key Features
- Elegant API: Chainable and readable HTTP request description
- Complete Swift Concurrency Support: async/await and Combine compatibility
- Comprehensive Authentication System: Basic, Digest, OAuth, and custom authentication support
- Type-Safe Codable Support: Automatic JSON/XML conversion and error handling
- SSL Certificate Pinning: Enterprise-level security requirements support
- Progress Monitoring: Real-time tracking of upload and download progress
Pros and Cons
Pros
- Overwhelming adoption rate in Swift/iOS/macOS ecosystem with abundant learning resources
- Simple and intuitive API design that hides complex URLSession processing
- Excellent integration with SwiftUI and Combine/async/await support
- Active community support and regular updates
- Enterprise-level authentication and security features with certificate pinning
- Rich ecosystem libraries including AlamofireImage and others
Cons
- Swift update dependency waiting due to third-party library dependence
- Learning cost and bundle size compared to URLSession native implementation
- Some limitations and Sendable requirement compliance issues with Swift 6.0+
- Feature limitations and non-support in Linux, Windows, and Android environments
- Necessity debate for simple cases due to evolution of iOS standard APIs
- Risk of excessive abstraction in large-scale projects
Reference Pages
Code Examples
Installation and Basic Setup
// Swift Package Manager (Recommended)
// Add to Package.swift
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.10.0"))
// Import Alamofire
import Alamofire
// Version check
print("Alamofire version: \(AFInfo.version)")
// CocoaPods
// Add to Podfile
// pod 'Alamofire', '~> 5.10'
// Carthage
// Add to Cartfile
// github "Alamofire/Alamofire" ~> 5.10
Basic Requests (GET/POST/PUT/DELETE)
import Alamofire
// Basic GET request
AF.request("https://api.example.com/users").response { response in
debugPrint(response)
}
// Type-safe JSON response processing
struct User: Codable {
let id: Int
let name: String
let email: String
}
AF.request("https://api.example.com/users")
.responseDecodable(of: [User].self) { response in
switch response.result {
case .success(let users):
print("Retrieved users count: \(users.count)")
users.forEach { user in
print("User: \(user.name) - \(user.email)")
}
case .failure(let error):
print("Error: \(error)")
}
}
// GET request with query parameters
let parameters: [String: Any] = [
"page": 1,
"limit": 20,
"sort": "created_at",
"order": "desc"
]
AF.request("https://api.example.com/users", parameters: parameters)
.responseDecodable(of: [User].self) { response in
debugPrint(response)
}
// POST request (sending JSON)
let newUser = [
"name": "John Doe",
"email": "[email protected]",
"age": 30
] as [String: Any]
AF.request("https://api.example.com/users",
method: .post,
parameters: newUser,
encoding: JSONEncoding.default)
.responseDecodable(of: User.self) { response in
switch response.result {
case .success(let createdUser):
print("User creation successful: \(createdUser.name)")
case .failure(let error):
print("Creation error: \(error)")
}
}
// PUT request (data update)
let updateData = ["name": "Jane Doe", "email": "[email protected]"]
AF.request("https://api.example.com/users/123",
method: .put,
parameters: updateData,
encoding: JSONEncoding.default)
.responseDecodable(of: User.self) { response in
debugPrint(response)
}
// DELETE request
AF.request("https://api.example.com/users/123", method: .delete)
.response { response in
if response.response?.statusCode == 204 {
print("User deletion completed")
} else {
print("Deletion error: \(response.error?.localizedDescription ?? "Unknown error")")
}
}
// Detailed response inspection
AF.request("https://api.example.com/users").response { response in
print("Status code: \(response.response?.statusCode ?? 0)")
print("Headers: \(response.response?.allHeaderFields ?? [:])")
print("Data size: \(response.data?.count ?? 0) bytes")
print("Execution time: \(response.metrics?.taskInterval.duration ?? 0) seconds")
}
Advanced Configuration and Customization (Headers, Authentication, Validation, etc.)
import Alamofire
// Custom header configuration
let headers: HTTPHeaders = [
"Authorization": "Bearer your-jwt-token",
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": "MyiOSApp/1.0",
"X-API-Version": "v2"
]
AF.request("https://api.example.com/protected-data", headers: headers)
.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
// Type-safe header configuration with HTTPHeaders
let typedHeaders: HTTPHeaders = [
.authorization(bearerToken: "your-jwt-token"),
.accept("application/json"),
.contentType("application/json"),
.userAgent("MyiOSApp/1.0")
]
// Basic authentication
AF.request("https://api.example.com/basic-auth")
.authenticate(username: "user", password: "password")
.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
// Authentication with URLCredential
let credential = URLCredential(user: "user", password: "password", persistence: .forSession)
AF.request("https://api.example.com/secure")
.authenticate(with: credential)
.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
// Response validation
AF.request("https://api.example.com/data")
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.responseDecodable(of: [String: Any].self) { response in
switch response.result {
case .success(let data):
print("Validation successful: \(data)")
case .failure(let error):
print("Validation error: \(error)")
}
}
// Custom validation
AF.request("https://api.example.com/data")
.validate { request, response, data in
// Custom validation logic
if response.statusCode == 200 {
return .success(())
} else {
let error = AFError.responseValidationFailed(reason: .unacceptableStatusCode(code: response.statusCode))
return .failure(error)
}
}
.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
// URLRequestModifier usage example
AF.request("https://api.example.com/data") { urlRequest in
urlRequest.timeoutInterval = 30
urlRequest.cachePolicy = .reloadIgnoringCacheData
urlRequest.setValue("en-US", forHTTPHeaderField: "Accept-Language")
}
.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
// SSL certificate pinning (using ServerTrustManager)
let evaluators: [String: ServerTrustEvaluating] = [
"api.example.com": PinnedCertificatesTrustEvaluator(),
"secure.example.com": PublicKeysTrustEvaluator()
]
let manager = ServerTrustManager(evaluators: evaluators)
let session = Session(serverTrustManager: manager)
session.request("https://api.example.com/secure-data")
.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
Error Handling and Retry Functionality
import Alamofire
// Comprehensive error handling
func performSafeRequest() {
AF.request("https://api.example.com/data")
.validate()
.responseDecodable(of: [String: Any].self) { response in
switch response.result {
case .success(let data):
print("Success: \(data)")
case .failure(let error):
// Detailed Alamofire error processing
if let afError = error.asAFError {
switch afError {
case .responseValidationFailed(let reason):
switch reason {
case .unacceptableStatusCode(let code):
print("Invalid status code: \(code)")
case .unacceptableContentType(let acceptableTypes, let responseType):
print("Invalid Content-Type: \(responseType), expected: \(acceptableTypes)")
default:
print("Response validation error: \(reason)")
}
case .sessionTaskFailed(let sessionError):
if let urlError = sessionError as? URLError {
switch urlError.code {
case .notConnectedToInternet:
print("No internet connection")
case .timedOut:
print("Request timed out")
case .cannotFindHost:
print("Cannot find host")
default:
print("Network error: \(urlError.localizedDescription)")
}
}
case .responseSerializationFailed(let reason):
print("Response serialization error: \(reason)")
default:
print("Alamofire error: \(afError)")
}
} else {
print("Other error: \(error)")
}
}
}
}
// Automatic retry functionality implementation
let retryPolicy = RetryPolicy(
retryLimit: 3,
exponentialBackoffBase: 2,
exponentialBackoffScale: 1.0,
retryableHTTPMethods: [.get, .post, .put, .delete],
retryableHTTPStatusCodes: [408, 429, 500, 502, 503, 504],
retryableURLErrorCodes: [
.timedOut, .cannotConnectToHost, .networkConnectionLost,
.dnsLookupFailed, .cannotFindHost
]
)
AF.request("https://api.example.com/unreliable", interceptor: retryPolicy)
.responseDecodable(of: [String: Any].self) { response in
switch response.result {
case .success(let data):
print("Retry successful: \(data)")
case .failure(let error):
print("Finally failed: \(error)")
}
}
// Custom RequestInterceptor implementation
class CustomRequestInterceptor: RequestInterceptor {
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var urlRequest = urlRequest
urlRequest.setValue("Bearer \(getAuthToken())", forHTTPHeaderField: "Authorization")
completion(.success(urlRequest))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
guard request.retryCount < 3 else {
completion(.doNotRetry)
return
}
if let response = request.task?.response as? HTTPURLResponse,
response.statusCode == 401 {
// For authentication errors, refresh token and retry
refreshAuthToken { success in
if success {
completion(.retry)
} else {
completion(.doNotRetry)
}
}
} else if let afError = error.asAFError,
case .sessionTaskFailed(let urlError as URLError) = afError,
[.timedOut, .cannotConnectToHost].contains(urlError.code) {
// For network errors, retry with exponential backoff
let delay = pow(2.0, Double(request.retryCount))
completion(.retryWithDelay(delay))
} else {
completion(.doNotRetry)
}
}
private func getAuthToken() -> String {
// Authentication token retrieval logic
return "current-auth-token"
}
private func refreshAuthToken(completion: @escaping (Bool) -> Void) {
// Token refresh logic
completion(true)
}
}
// Using custom interceptor
let interceptor = CustomRequestInterceptor()
AF.request("https://api.example.com/protected", interceptor: interceptor)
.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
File Upload and Download
import Alamofire
import UIKit
// Data upload
let imageData = UIImage(named: "sample-image")?.jpegData(compressionQuality: 0.8)
AF.upload(imageData!, to: "https://api.example.com/upload")
.uploadProgress { progress in
print("Upload progress: \(progress.fractionCompleted * 100)%")
}
.responseDecodable(of: [String: Any].self) { response in
switch response.result {
case .success(let result):
print("Upload successful: \(result)")
case .failure(let error):
print("Upload error: \(error)")
}
}
// File upload
let fileURL = Bundle.main.url(forResource: "document", withExtension: "pdf")!
AF.upload(fileURL, to: "https://api.example.com/upload-file")
.uploadProgress(queue: .main) { progress in
DispatchQueue.main.async {
// UI progress update
print("File upload: \(Int(progress.fractionCompleted * 100))%")
}
}
.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
// Multipart form data upload
AF.upload(multipartFormData: { multipartFormData in
// Text data
multipartFormData.append("John Doe".data(using: .utf8)!, withName: "name")
multipartFormData.append("[email protected]".data(using: .utf8)!, withName: "email")
// File data
if let imageData = UIImage(named: "profile")?.jpegData(compressionQuality: 0.8) {
multipartFormData.append(imageData, withName: "avatar", fileName: "profile.jpg", mimeType: "image/jpeg")
}
// Document file
if let documentURL = Bundle.main.url(forResource: "resume", withExtension: "pdf") {
multipartFormData.append(documentURL, withName: "resume", fileName: "resume.pdf", mimeType: "application/pdf")
}
}, to: "https://api.example.com/profile") { result in
switch result {
case .success(let upload, _, _):
upload.uploadProgress { progress in
print("Multipart progress: \(progress.fractionCompleted * 100)%")
}
upload.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
case .failure(let error):
print("Multipart error: \(error)")
}
}
// File download
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("downloaded-file.zip")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
AF.download("https://api.example.com/large-file.zip", to: destination)
.downloadProgress(queue: .main) { progress in
print("Download progress: \(Int(progress.fractionCompleted * 100))%")
print("Downloaded: \(ByteCountFormatter.string(fromByteCount: progress.completedUnitCount, countStyle: .file))")
print("Total size: \(ByteCountFormatter.string(fromByteCount: progress.totalUnitCount, countStyle: .file))")
}
.response { response in
if response.error == nil, let filePath = response.fileURL?.path {
print("Download completed: \(filePath)")
} else {
print("Download error: \(response.error?.localizedDescription ?? "Unknown error")")
}
}
// Download pause and resume
var downloadRequest: DownloadRequest?
downloadRequest = AF.download("https://api.example.com/large-file.zip")
.downloadProgress { progress in
print("Progress: \(progress.fractionCompleted)")
}
.response { response in
print("Download completed or interrupted")
}
// Pause and get resume data
downloadRequest?.cancel { resumeData in
if let resumeData = resumeData {
// Resume using resume data
AF.download(resumingWith: resumeData)
.downloadProgress { progress in
print("Resume progress: \(progress.fractionCompleted)")
}
.response { response in
print("Resume download completed")
}
}
}
Swift Concurrency (async/await) Support
import Alamofire
// Basic request using async/await
func fetchUsersAsync() async throws -> [User] {
let response = await AF.request("https://api.example.com/users")
.validate()
.serializingDecodable([User].self)
.response
switch response.result {
case .success(let users):
return users
case .failure(let error):
throw error
}
}
// Usage example
Task {
do {
let users = try await fetchUsersAsync()
print("Retrieved users: \(users.count)")
} catch {
print("Error: \(error)")
}
}
// Parallel processing of multiple asynchronous requests
func fetchMultipleDataAsync() async throws -> (users: [User], posts: [Post]) {
async let usersResponse = AF.request("https://api.example.com/users")
.validate()
.serializingDecodable([User].self)
.response
async let postsResponse = AF.request("https://api.example.com/posts")
.validate()
.serializingDecodable([Post].self)
.response
let (usersResult, postsResult) = await (usersResponse, postsResponse)
guard case .success(let users) = usersResult.result,
case .success(let posts) = postsResult.result else {
throw AFError.responseSerializationFailed(reason: .inputDataNil)
}
return (users: users, posts: posts)
}
// async/await upload
func uploadImageAsync(_ imageData: Data) async throws -> [String: Any] {
let response = await AF.upload(imageData, to: "https://api.example.com/upload")
.validate()
.serializingDecodable([String: Any].self)
.response
switch response.result {
case .success(let result):
return result
case .failure(let error):
throw error
}
}
// Parallel upload of multiple files using TaskGroup
func uploadMultipleFilesAsync(_ files: [Data]) async throws -> [[String: Any]] {
return try await withThrowingTaskGroup(of: [String: Any].self) { group in
for (index, fileData) in files.enumerated() {
group.addTask {
return try await self.uploadImageAsync(fileData)
}
}
var results: [[String: Any]] = []
for try await result in group {
results.append(result)
}
return results
}
}
// Streaming using AsyncSequence (experimental)
func streamDataAsync() async throws {
let request = AF.streamRequest("https://api.example.com/stream")
for try await data in request.asAsyncSequence() {
// Process streaming data
print("Received data: \(data.count) bytes")
}
}
Combine Integration
import Alamofire
import Combine
// Basic request using Combine
func fetchUsersPublisher() -> AnyPublisher<[User], AFError> {
return AF.request("https://api.example.com/users")
.validate()
.publishDecodable(type: [User].self)
.value()
.eraseToAnyPublisher()
}
// Usage example
var cancellables = Set<AnyCancellable>()
fetchUsersPublisher()
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { completion in
switch completion {
case .finished:
print("Request completed")
case .failure(let error):
print("Error: \(error)")
}
},
receiveValue: { users in
print("Users retrieved: \(users.count)")
}
)
.store(in: &cancellables)
// Combining multiple requests
Publishers.Zip(
AF.request("https://api.example.com/users").publishDecodable(type: [User].self).value(),
AF.request("https://api.example.com/posts").publishDecodable(type: [Post].self).value()
)
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { completion in
print("Combined request completion: \(completion)")
},
receiveValue: { users, posts in
print("Data retrieved - Users: \(users.count), Posts: \(posts.count)")
}
)
.store(in: &cancellables)
// Chain requests (fetch users → fetch details)
fetchUsersPublisher()
.flatMap { users -> AnyPublisher<[UserDetail], AFError> in
let userIds = users.map { $0.id }
return AF.request("https://api.example.com/users/details",
parameters: ["ids": userIds])
.publishDecodable(type: [UserDetail].self)
.value()
.eraseToAnyPublisher()
}
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { completion in
print("Chain request completion: \(completion)")
},
receiveValue: { userDetails in
print("Detail data retrieved: \(userDetails.count) items")
}
)
.store(in: &cancellables)