Alamofire

Swift向けの最も人気のサードパーティHTTPネットワークライブラリ。URLSessionベースで構築されながら、より使いやすく優雅なAPIを提供。チェーン可能なリクエスト、自動リトライ、応答検証、JSONシリアライゼーション、Swift Concurrency対応。

HTTPクライアントSwiftiOSmacOS非同期処理認証

GitHub概要

Alamofire/Alamofire

Elegant HTTP Networking in Swift

スター42,173
ウォッチ1,056
フォーク7,642
作成日:2014年7月31日
言語:Swift
ライセンス:MIT License

トピックス

alamofirecarthagecertificate-pinningcocoapodshttpurlresponsenetworkingparameter-encodingpublic-key-pinningrequestresponseswiftswift-package-managerurlrequesturlsessionxcode

スター履歴

Alamofire/Alamofire Star History
データ取得日時: 2025/10/22 04:10

ライブラリ

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)