Alamofire
Swift向けの最も人気のサードパーティHTTPネットワークライブラリ。URLSessionベースで構築されながら、より使いやすく優雅なAPIを提供。チェーン可能なリクエスト、自動リトライ、応答検証、JSONシリアライゼーション、Swift Concurrency対応。
GitHub概要
トピックス
スター履歴
ライブラリ
Alamofire
概要
Alamofireは「Elegant HTTP Networking in Swift」をコンセプトとして開発された、Swift言語向けの高機能HTTPネットワーキングライブラリです。iOS・macOS開発において事実上の標準ライブラリとして圧倒的な普及率を誇り、URLSessionの複雑な処理をシンプルで直感的なAPIで包み込んだ設計が特徴。チェーン可能なメソッド、自動的なJSON変換、認証システム、SSL証明書ピニング、進捗監視など企業レベルのHTTP通信要件を満たす包括的な機能を提供し、Swift開発者の生産性向上に大きく貢献しています。
詳細
Alamofire 2025年版は最新のSwift 6.0対応とSwift Concurrency完全サポートにより、現代的なiOS・macOS開発の決定版HTTPライブラリとして進化を続けています。10年以上の開発実績により成熟したAPIと卓越した安定性を誇り、iOS 8.0+からiOS 18.0+まで幅広いプラットフォームをサポート。URLSessionベースの堅牢なアーキテクチャに加え、チェーン可能な構文、型安全なCodableサポート、自動リトライ機能、WebSocket実験サポートなど最新技術を統合。Apple標準のURLSessionと比較して大幅な開発効率向上を実現し、Swift Package Manager・CocoaPods・Carthageによる柔軟な導入が可能です。
主な特徴
- エレガントなAPI: チェーン可能で読みやすいHTTPリクエスト記述
- Swift Concurrency完全サポート: async/awaitとCombine対応
- 包括的認証システム: Basic・Digest・OAuth・カスタム認証対応
- 型安全なCodableサポート: 自動的なJSON・XML変換とエラー処理
- SSL証明書ピニング: 企業レベルのセキュリティ要件対応
- 進捗監視機能: アップロード・ダウンロード進捗のリアルタイム追跡
メリット・デメリット
メリット
- Swift/iOS/macOSエコシステムでの圧倒的な普及率と豊富な学習リソース
- URLSessionの複雑な処理を隠蔽したシンプルで直感的なAPI設計
- SwiftUIとの優れた統合性とCombine・async/awaitサポート
- 活発なコミュニティサポートと定期的なアップデート
- 企業レベルの認証・セキュリティ機能と証明書ピニング
- AlamofireImage等の豊富なエコシステムライブラリ
デメリット
- サードパーティライブラリ依存によるSwiftアップデート待ち
- URLSessionネイティブ実装と比較した学習コストとバンドルサイズ
- Swift 6.0以降での一部制限とSendable要件対応課題
- Linux・Windows・Android環境での機能制限と非サポート
- iOS標準APIの進化によりシンプルなケースでの必要性議論
- 大規模プロジェクトでの過度な抽象化リスク
参考ページ
書き方の例
インストールと基本セットアップ
// Swift Package Manager (推奨)
// Package.swift に追加
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.10.0"))
// Alamofireをインポート
import Alamofire
// バージョン確認
print("Alamofire version: \(AFInfo.version)")
// CocoaPods
// Podfile に追加
// pod 'Alamofire', '~> 5.10'
// Carthage
// Cartfile に追加
// github "Alamofire/Alamofire" ~> 5.10
基本的なリクエスト(GET/POST/PUT/DELETE)
import Alamofire
// 基本的なGETリクエスト
AF.request("https://api.example.com/users").response { response in
debugPrint(response)
}
// JSONレスポンスを型安全に処理
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("取得したユーザー数: \(users.count)")
users.forEach { user in
print("ユーザー: \(user.name) - \(user.email)")
}
case .failure(let error):
print("エラー: \(error)")
}
}
// クエリパラメータ付きGETリクエスト
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リクエスト(JSON送信)
let newUser = [
"name": "田中太郎",
"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("ユーザー作成成功: \(createdUser.name)")
case .failure(let error):
print("作成エラー: \(error)")
}
}
// PUTリクエスト(データ更新)
let updateData = ["name": "田中次郎", "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リクエスト
AF.request("https://api.example.com/users/123", method: .delete)
.response { response in
if response.response?.statusCode == 204 {
print("ユーザー削除完了")
} else {
print("削除エラー: \(response.error?.localizedDescription ?? "Unknown error")")
}
}
// レスポンス詳細確認
AF.request("https://api.example.com/users").response { response in
print("ステータスコード: \(response.response?.statusCode ?? 0)")
print("ヘッダー: \(response.response?.allHeaderFields ?? [:])")
print("データサイズ: \(response.data?.count ?? 0) bytes")
print("実行時間: \(response.metrics?.taskInterval.duration ?? 0) seconds")
}
高度な設定とカスタマイズ(ヘッダー、認証、バリデーション等)
import Alamofire
// カスタムヘッダーの設定
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)
}
// HTTPHeaders型による型安全なヘッダー設定
let typedHeaders: HTTPHeaders = [
.authorization(bearerToken: "your-jwt-token"),
.accept("application/json"),
.contentType("application/json"),
.userAgent("MyiOSApp/1.0")
]
// Basic認証
AF.request("https://api.example.com/basic-auth")
.authenticate(username: "user", password: "password")
.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
// 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)
}
// レスポンスバリデーション
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("バリデーション成功: \(data)")
case .failure(let error):
print("バリデーションエラー: \(error)")
}
}
// カスタムバリデーション
AF.request("https://api.example.com/data")
.validate { request, response, data in
// カスタムバリデーションロジック
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使用例
AF.request("https://api.example.com/data") { urlRequest in
urlRequest.timeoutInterval = 30
urlRequest.cachePolicy = .reloadIgnoringCacheData
urlRequest.setValue("ja-JP", forHTTPHeaderField: "Accept-Language")
}
.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
// SSL証明書ピニング(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)
}
エラーハンドリングとリトライ機能
import Alamofire
// 包括的なエラーハンドリング
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("成功: \(data)")
case .failure(let error):
// Alamofireエラーの詳細処理
if let afError = error.asAFError {
switch afError {
case .responseValidationFailed(let reason):
switch reason {
case .unacceptableStatusCode(let code):
print("不正なステータスコード: \(code)")
case .unacceptableContentType(let acceptableTypes, let responseType):
print("不正なContent-Type: \(responseType), 期待値: \(acceptableTypes)")
default:
print("レスポンスバリデーションエラー: \(reason)")
}
case .sessionTaskFailed(let sessionError):
if let urlError = sessionError as? URLError {
switch urlError.code {
case .notConnectedToInternet:
print("インターネット接続がありません")
case .timedOut:
print("リクエストがタイムアウトしました")
case .cannotFindHost:
print("ホストが見つかりません")
default:
print("ネットワークエラー: \(urlError.localizedDescription)")
}
}
case .responseSerializationFailed(let reason):
print("レスポンス変換エラー: \(reason)")
default:
print("Alamofireエラー: \(afError)")
}
} else {
print("その他のエラー: \(error)")
}
}
}
}
// 自動リトライ機能の実装
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("リトライ成功: \(data)")
case .failure(let error):
print("最終的に失敗: \(error)")
}
}
// カスタムRequestInterceptorの実装
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 {
// 認証エラーの場合、トークンを更新してリトライ
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) {
// ネットワークエラーの場合は指数バックオフでリトライ
let delay = pow(2.0, Double(request.retryCount))
completion(.retryWithDelay(delay))
} else {
completion(.doNotRetry)
}
}
private func getAuthToken() -> String {
// 認証トークン取得ロジック
return "current-auth-token"
}
private func refreshAuthToken(completion: @escaping (Bool) -> Void) {
// トークン更新ロジック
completion(true)
}
}
// カスタムインターセプタの使用
let interceptor = CustomRequestInterceptor()
AF.request("https://api.example.com/protected", interceptor: interceptor)
.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
ファイルアップロードとダウンロード
import Alamofire
import UIKit
// データのアップロード
let imageData = UIImage(named: "sample-image")?.jpegData(compressionQuality: 0.8)
AF.upload(imageData!, to: "https://api.example.com/upload")
.uploadProgress { progress in
print("アップロード進捗: \(progress.fractionCompleted * 100)%")
}
.responseDecodable(of: [String: Any].self) { response in
switch response.result {
case .success(let result):
print("アップロード成功: \(result)")
case .failure(let error):
print("アップロードエラー: \(error)")
}
}
// ファイルのアップロード
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の進捗更新
print("ファイルアップロード: \(Int(progress.fractionCompleted * 100))%")
}
}
.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
// マルチパートフォームデータのアップロード
AF.upload(multipartFormData: { multipartFormData in
// テキストデータ
multipartFormData.append("田中太郎".data(using: .utf8)!, withName: "name")
multipartFormData.append("[email protected]".data(using: .utf8)!, withName: "email")
// ファイルデータ
if let imageData = UIImage(named: "profile")?.jpegData(compressionQuality: 0.8) {
multipartFormData.append(imageData, withName: "avatar", fileName: "profile.jpg", mimeType: "image/jpeg")
}
// ドキュメントファイル
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("マルチパート進捗: \(progress.fractionCompleted * 100)%")
}
upload.responseDecodable(of: [String: Any].self) { response in
debugPrint(response)
}
case .failure(let error):
print("マルチパートエラー: \(error)")
}
}
// ファイルダウンロード
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("ダウンロード進捗: \(Int(progress.fractionCompleted * 100))%")
print("ダウンロード済み: \(ByteCountFormatter.string(fromByteCount: progress.completedUnitCount, countStyle: .file))")
print("総サイズ: \(ByteCountFormatter.string(fromByteCount: progress.totalUnitCount, countStyle: .file))")
}
.response { response in
if response.error == nil, let filePath = response.fileURL?.path {
print("ダウンロード完了: \(filePath)")
} else {
print("ダウンロードエラー: \(response.error?.localizedDescription ?? "Unknown error")")
}
}
// ダウンロードの一時停止と再開
var downloadRequest: DownloadRequest?
downloadRequest = AF.download("https://api.example.com/large-file.zip")
.downloadProgress { progress in
print("進捗: \(progress.fractionCompleted)")
}
.response { response in
print("ダウンロード完了または中断")
}
// 一時停止してレジュームデータ取得
downloadRequest?.cancel { resumeData in
if let resumeData = resumeData {
// レジュームデータを使って再開
AF.download(resumingWith: resumeData)
.downloadProgress { progress in
print("再開後の進捗: \(progress.fractionCompleted)")
}
.response { response in
print("再開ダウンロード完了")
}
}
}
Swift Concurrency(async/await)サポート
import Alamofire
// 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
}
}
// 使用例
Task {
do {
let users = try await fetchUsersAsync()
print("取得したユーザー: \(users.count)人")
} catch {
print("エラー: \(error)")
}
}
// 複数の非同期リクエストの並列処理
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でのアップロード
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
}
}
// 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
}
}
// AsyncSequenceを使ったストリーミング(実験的)
func streamDataAsync() async throws {
let request = AF.streamRequest("https://api.example.com/stream")
for try await data in request.asAsyncSequence() {
// ストリーミングデータの処理
print("受信データ: \(data.count) bytes")
}
}
Combineとの統合
import Alamofire
import Combine
// Combineを使った基本リクエスト
func fetchUsersPublisher() -> AnyPublisher<[User], AFError> {
return AF.request("https://api.example.com/users")
.validate()
.publishDecodable(type: [User].self)
.value()
.eraseToAnyPublisher()
}
// 使用例
var cancellables = Set<AnyCancellable>()
fetchUsersPublisher()
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { completion in
switch completion {
case .finished:
print("リクエスト完了")
case .failure(let error):
print("エラー: \(error)")
}
},
receiveValue: { users in
print("ユーザー取得: \(users.count)人")
}
)
.store(in: &cancellables)
// 複数リクエストの組み合わせ
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("組み合わせリクエスト完了: \(completion)")
},
receiveValue: { users, posts in
print("データ取得 - ユーザー: \(users.count), 投稿: \(posts.count)")
}
)
.store(in: &cancellables)
// チェーンリクエスト(ユーザー取得 → 詳細取得)
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("チェーンリクエスト完了: \(completion)")
},
receiveValue: { userDetails in
print("詳細データ取得: \(userDetails.count)件")
}
)
.store(in: &cancellables)