URLSession

Appleが提供するiOS/macOS標準のHTTPクライアントAPI。Foundationフレームワークの一部として、ネイティブで高性能なHTTP通信を実現。async/await対応、HTTP/2サポート、バックグラウンド転送、詳細な設定オプション、Cookie管理機能を内蔵。

HTTPクライアントSwift標準ライブラリFoundationiOSmacOS

GitHub概要

swiftlang/swift

The Swift Programming Language

ホームページ:https://swift.org
スター69,178
ウォッチ2,433
フォーク10,560
作成日:2015年10月23日
言語:C++
ライセンス:Apache License 2.0

トピックス

なし

スター履歴

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

ライブラリ

URLSession

概要

URLSessionはSwift Foundationフレームワークの一部として提供される「ネットワークセッションの設定と管理のためのクラス群」です。AppleプラットフォームにおけるHTTPクライアントの事実上の標準として、iOS、macOS、watchOS、tvOSアプリでのネットワーク通信を支援。URL によって識別されるエンドポイントとの間でデータの非同期ダウンロード・アップロードを可能にし、WebSocket通信、バックグラウンドダウンロード、証明書ピニングなど企業レベルのネットワーク機能を包括的にサポートしています。

詳細

URLSession 2025年版は Swift Concurrency (async/await) との完全統合により、従来のコールバックベースAPIに加えて現代的な非同期プログラミングパターンを提供しています。URLSessionDataTask、URLSessionUploadTask、URLSessionDownloadTaskの3つの主要なタスクタイプにより、基本的なHTTPリクエストから大容量ファイル転送まで幅広いネットワーク通信ニーズに対応。バックグラウンド実行、App Transport Security (ATS)、HTTP/2サポート、カスタムプロトコル対応など、モバイルアプリケーション特有の要件を満たす高度な機能を標準で提供します。

主な特徴

  • 包括的なタスクタイプ: Data、Upload、Download、WebSocketの多様な通信パターン
  • Swift Concurrency統合: async/await による現代的な非同期プログラミング
  • バックグラウンド処理: アプリ非アクティブ時の継続的なデータ転送
  • セキュリティ強化: ATS、証明書ピニング、TLS 1.3対応
  • HTTP/2・HTTP/3サポート: 最新プロトコルによる高速通信
  • Combineフレームワーク統合: Reactive programmingパターン対応

メリット・デメリット

メリット

  • Apple プラットフォーム標準による完全統合と最適化
  • Swift Concurrency との自然な統合による優れた開発体験
  • バックグラウンドダウンロードとモバイル特化機能の充実
  • App Store審査で問題となるセキュリティ要件を標準でクリア
  • WebSocketやHTTP/3など最新プロトコルの先進的サポート
  • メモリ管理とパフォーマンスの最適化が Apple によって保証

デメリット

  • Apple プラットフォームに限定され他OSでは利用不可
  • 過度に詳細な設定が必要で軽量な用途には複雑
  • デバッグ時のネットワークログが限定的
  • サードパーティライブラリと比較してカスタマイズ性に制約
  • レガシーなAPIとの共存による学習コストの増大
  • SwiftUIとの直接的な統合機能が限定的

参考ページ

書き方の例

基本設定とインポート

import Foundation
import Combine // Publisher使用時

// iOS 15+ / macOS 12+ でのasync/await対応
@available(iOS 15.0, macOS 12.0, *)
class NetworkManager {
    static let shared = NetworkManager()
    
    private init() {}
    
    // 基本的なURLSessionインスタンス
    private let session = URLSession.shared
    
    // カスタム設定のURLSession
    private lazy var customSession: URLSession = {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 30
        config.timeoutIntervalForResource = 60
        config.waitsForConnectivity = true
        
        return URLSession(configuration: config)
    }()
}

// レスポンス用のSwift構造体
struct User: Codable {
    let id: Int
    let name: String
    let email: String
    let age: Int?
}

struct APIResponse<T: Codable>: Codable {
    let success: Bool
    let data: T
    let message: String
}

struct ErrorResponse: Codable {
    let error: String
    let code: Int
    let details: String?
}

基本的なリクエスト(GET/POST/PUT/DELETE)

import Foundation

extension NetworkManager {
    
    // MARK: - Async/Await API (iOS 15+)
    
    // 基本的なGETリクエスト
    func fetchUsers() async throws -> [User] {
        guard let url = URL(string: "https://api.example.com/users") else {
            throw URLError(.badURL)
        }
        
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        request.setValue("Bearer your-token", forHTTPHeaderField: "Authorization")
        
        let (data, response) = try await session.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse else {
            throw URLError(.badServerResponse)
        }
        
        guard httpResponse.statusCode == 200 else {
            throw URLError(.init(rawValue: httpResponse.statusCode))
        }
        
        let users = try JSONDecoder().decode([User].self, from: data)
        return users
    }
    
    // クエリパラメータ付きGETリクエスト
    func fetchUsersWithParams(page: Int, limit: Int) async throws -> [User] {
        var components = URLComponents(string: "https://api.example.com/users")!
        components.queryItems = [
            URLQueryItem(name: "page", value: String(page)),
            URLQueryItem(name: "limit", value: String(limit)),
            URLQueryItem(name: "sort", value: "created_at")
        ]
        
        guard let url = components.url else {
            throw URLError(.badURL)
        }
        
        let (data, response) = try await session.data(from: url)
        
        if let httpResponse = response as? HTTPURLResponse {
            print("レスポンスステータス: \(httpResponse.statusCode)")
            print("レスポンスヘッダー: \(httpResponse.allHeaderFields)")
        }
        
        return try JSONDecoder().decode([User].self, from: data)
    }
    
    // POSTリクエスト(JSON送信)
    func createUser(_ user: User) async throws -> User {
        guard let url = URL(string: "https://api.example.com/users") else {
            throw URLError(.badURL)
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        request.setValue("Bearer your-token", forHTTPHeaderField: "Authorization")
        
        // リクエストボディのJSONエンコード
        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = .iso8601
        request.httpBody = try encoder.encode(user)
        
        let (data, response) = try await session.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse else {
            throw URLError(.badServerResponse)
        }
        
        if httpResponse.statusCode == 201 {
            let createdUser = try JSONDecoder().decode(User.self, from: data)
            print("ユーザー作成完了: \(createdUser.name)")
            return createdUser
        } else {
            // エラーレスポンスの処理
            let errorResponse = try JSONDecoder().decode(ErrorResponse.self, from: data)
            throw NSError(domain: "APIError", code: errorResponse.code, userInfo: [
                NSLocalizedDescriptionKey: errorResponse.error
            ])
        }
    }
    
    // PUTリクエスト(データ更新)
    func updateUser(id: Int, updates: [String: Any]) async throws -> User {
        guard let url = URL(string: "https://api.example.com/users/\(id)") else {
            throw URLError(.badURL)
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = "PUT"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("Bearer your-token", forHTTPHeaderField: "Authorization")
        
        // Dictionaryを直接JSONに変換
        request.httpBody = try JSONSerialization.data(withJSONObject: updates)
        
        let (data, response) = try await session.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        
        return try JSONDecoder().decode(User.self, from: data)
    }
    
    // DELETEリクエスト
    func deleteUser(id: Int) async throws {
        guard let url = URL(string: "https://api.example.com/users/\(id)") else {
            throw URLError(.badURL)
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = "DELETE"
        request.setValue("Bearer your-token", forHTTPHeaderField: "Authorization")
        
        let (_, response) = try await session.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 204 else {
            throw URLError(.badServerResponse)
        }
        
        print("ユーザー削除完了")
    }
}

// MARK: - 従来のコールバックベースAPI(iOS 13+)

extension NetworkManager {
    
    // レガシーサポート用のコールバックベースリクエスト
    func fetchUsersLegacy(completion: @escaping (Result<[User], Error>) -> Void) {
        guard let url = URL(string: "https://api.example.com/users") else {
            completion(.failure(URLError(.badURL)))
            return
        }
        
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        
        let task = session.dataTask(with: request) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            guard let data = data,
                  let httpResponse = response as? HTTPURLResponse,
                  httpResponse.statusCode == 200 else {
                completion(.failure(URLError(.badServerResponse)))
                return
            }
            
            do {
                let users = try JSONDecoder().decode([User].self, from: data)
                completion(.success(users))
            } catch {
                completion(.failure(error))
            }
        }
        
        task.resume()
    }
}

高度な設定とカスタマイズ(認証、タイムアウト、キャッシュ等)

import Foundation
import Network

class AdvancedNetworkManager {
    
    // MARK: - カスタムURLSessionConfiguration
    
    static func createCustomSession() -> URLSession {
        let config = URLSessionConfiguration.default
        
        // タイムアウト設定
        config.timeoutIntervalForRequest = 30.0
        config.timeoutIntervalForResource = 120.0
        
        // 接続性設定
        config.waitsForConnectivity = true
        config.allowsCellularAccess = true
        config.allowsExpensiveNetworkAccess = true
        config.allowsConstrainedNetworkAccess = false
        
        // キャッシュ設定
        let cache = URLCache(
            memoryCapacity: 50 * 1024 * 1024,    // 50MB
            diskCapacity: 200 * 1024 * 1024,     // 200MB
            diskPath: "networking_cache"
        )
        config.urlCache = cache
        config.requestCachePolicy = .reloadIgnoringLocalCacheData
        
        // ヘッダー設定
        config.httpAdditionalHeaders = [
            "User-Agent": "MyApp/1.0 (iOS)",
            "Accept-Language": "ja-JP,en-US",
            "X-API-Version": "v2"
        ]
        
        // HTTP設定
        config.httpMaximumConnectionsPerHost = 6
        config.httpShouldUsePipelining = true
        config.httpShouldSetCookies = true
        config.httpCookieAcceptPolicy = .always
        
        return URLSession(configuration: config)
    }
    
    // MARK: - バックグラウンドダウンロード設定
    
    static func createBackgroundSession() -> URLSession {
        let config = URLSessionConfiguration.background(
            withIdentifier: "com.myapp.background-download"
        )
        
        // バックグラウンド専用設定
        config.isDiscretionary = true  // システム最適化を有効
        config.sessionSendsLaunchEvents = true
        
        // 接続設定
        config.waitsForConnectivity = true
        config.allowsCellularAccess = false  // WiFiのみ
        
        return URLSession(configuration: config, delegate: nil, delegateQueue: nil)
    }
    
    // MARK: - 認証機能
    
    class AuthenticationManager: NSObject, URLSessionDelegate {
        
        // HTTP基本認証
        func urlSession(
            _ session: URLSession,
            task: URLSessionTask,
            didReceive challenge: URLAuthenticationChallenge,
            completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
        ) {
            
            let authenticationMethod = challenge.protectionSpace.authenticationMethod
            
            switch authenticationMethod {
            case NSURLAuthenticationMethodHTTPBasic:
                let credential = URLCredential(
                    user: "username",
                    password: "password",
                    persistence: .forSession
                )
                completionHandler(.useCredential, credential)
                
            case NSURLAuthenticationMethodServerTrust:
                // SSL証明書ピニング
                handleServerTrust(challenge: challenge, completionHandler: completionHandler)
                
            default:
                completionHandler(.performDefaultHandling, nil)
            }
        }
        
        private func handleServerTrust(
            challenge: URLAuthenticationChallenge,
            completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
        ) {
            
            guard let serverTrust = challenge.protectionSpace.serverTrust else {
                completionHandler(.performDefaultHandling, nil)
                return
            }
            
            // 証明書ピニングのカスタム検証
            let policy = SecPolicyCreateSSL(true, "api.example.com" as CFString)
            SecTrustSetPolicies(serverTrust, policy)
            
            var secResult = SecTrustResultType.invalid
            let status = SecTrustEvaluate(serverTrust, &secResult)
            
            if status == errSecSuccess &&
               (secResult == .unspecified || secResult == .proceed) {
                let credential = URLCredential(trust: serverTrust)
                completionHandler(.useCredential, credential)
            } else {
                completionHandler(.cancelAuthenticationChallenge, nil)
            }
        }
    }
    
    // MARK: - カスタムHTTPヘッダー管理
    
    func createAuthenticatedRequest(url: URL, token: String) -> URLRequest {
        var request = URLRequest(url: url)
        
        // 認証ヘッダー
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        
        // カスタムヘッダー
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue(UUID().uuidString, forHTTPHeaderField: "X-Request-ID")
        request.setValue("MyApp/1.0", forHTTPHeaderField: "User-Agent")
        
        // キャッシュ制御
        request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData
        
        return request
    }
    
    // MARK: - プロキシ設定(iOS 14+)
    
    @available(iOS 14.0, *)
    func configureProxySettings() -> URLSessionConfiguration {
        let config = URLSessionConfiguration.default
        
        // HTTPプロキシ設定
        let proxySettings: [String: Any] = [
            kCFNetworkProxiesHTTPEnable as String: true,
            kCFNetworkProxiesHTTPProxy as String: "proxy.example.com",
            kCFNetworkProxiesHTTPPort as String: 8080,
            kCFNetworkProxiesHTTPSEnable as String: true,
            kCFNetworkProxiesHTTPSProxy as String: "proxy.example.com",
            kCFNetworkProxiesHTTPSPort as String: 8080
        ]
        
        config.connectionProxyDictionary = proxySettings
        
        return config
    }
}

エラーハンドリングとリトライ機能

import Foundation
import os.log

// カスタムエラー定義
enum NetworkError: LocalizedError {
    case invalidURL
    case noData
    case decodingError(Error)
    case serverError(Int, String?)
    case networkUnavailable
    case timeout
    case unauthorized
    case rateLimited(retryAfter: TimeInterval?)
    
    var errorDescription: String? {
        switch self {
        case .invalidURL:
            return "無効なURLです"
        case .noData:
            return "データが取得できませんでした"
        case .decodingError(let error):
            return "データの解析に失敗しました: \(error.localizedDescription)"
        case .serverError(let code, let message):
            return "サーバーエラー (\(code)): \(message ?? "不明なエラー")"
        case .networkUnavailable:
            return "ネットワークに接続できません"
        case .timeout:
            return "リクエストがタイムアウトしました"
        case .unauthorized:
            return "認証が必要です"
        case .rateLimited(let retryAfter):
            let after = retryAfter.map { String(format: "%.0f秒後", $0) } ?? "しばらく"
            return "アクセス制限中です。\(after)に再試行してください"
        }
    }
}

class RobustNetworkManager {
    private let session: URLSession
    private let logger = Logger(subsystem: "com.myapp.networking", category: "api")
    
    init() {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 30
        config.waitsForConnectivity = true
        self.session = URLSession(configuration: config)
    }
    
    // MARK: - リトライ機能付きリクエスト
    
    func fetchWithRetry<T: Codable>(
        url: URL,
        type: T.Type,
        maxRetries: Int = 3,
        backoffMultiplier: Double = 2.0
    ) async throws -> T {
        
        var lastError: Error?
        
        for attempt in 0...maxRetries {
            do {
                logger.info("API request attempt \(attempt + 1)/\(maxRetries + 1): \(url)")
                
                let (data, response) = try await session.data(from: url)
                
                guard let httpResponse = response as? HTTPURLResponse else {
                    throw NetworkError.serverError(0, "無効なレスポンス")
                }
                
                // ステータスコード別の処理
                switch httpResponse.statusCode {
                case 200...299:
                    logger.info("API request successful: \(httpResponse.statusCode)")
                    return try JSONDecoder().decode(type, from: data)
                    
                case 401:
                    throw NetworkError.unauthorized
                    
                case 429:
                    let retryAfter = httpResponse.value(forHTTPHeaderField: "Retry-After")
                        .flatMap { Double($0) }
                    throw NetworkError.rateLimited(retryAfter: retryAfter)
                    
                case 500...599:
                    if attempt < maxRetries {
                        let delay = pow(backoffMultiplier, Double(attempt))
                        logger.warning("Server error \(httpResponse.statusCode), retrying in \(delay)s")
                        try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
                        continue
                    }
                    throw NetworkError.serverError(httpResponse.statusCode, nil)
                    
                default:
                    throw NetworkError.serverError(httpResponse.statusCode, nil)
                }
                
            } catch let error as NetworkError {
                lastError = error
                
                // リトライ不可能なエラー
                if case .unauthorized = error, case .rateLimited = error {
                    throw error
                }
                
                if attempt < maxRetries {
                    let delay = pow(backoffMultiplier, Double(attempt))
                    logger.warning("Request failed: \(error), retrying in \(delay)s")
                    try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
                }
                
            } catch {
                lastError = error
                
                // ネットワークエラーの場合はリトライ
                if (error as NSError).domain == NSURLErrorDomain {
                    if attempt < maxRetries {
                        let delay = pow(backoffMultiplier, Double(attempt))
                        logger.warning("Network error: \(error), retrying in \(delay)s")
                        try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
                        continue
                    }
                }
                
                throw error
            }
        }
        
        throw lastError ?? NetworkError.networkUnavailable
    }
    
    // MARK: - 包括的エラーハンドリング
    
    func handleResponse<T: Codable>(
        data: Data,
        response: URLResponse,
        type: T.Type
    ) throws -> T {
        
        guard let httpResponse = response as? HTTPURLResponse else {
            throw NetworkError.serverError(0, "HTTPレスポンスではありません")
        }
        
        logger.info("Response: \(httpResponse.statusCode) \(HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode))")
        
        switch httpResponse.statusCode {
        case 200...299:
            do {
                return try JSONDecoder().decode(type, from: data)
            } catch {
                logger.error("JSON decoding failed: \(error)")
                throw NetworkError.decodingError(error)
            }
            
        case 400:
            if let errorData = try? JSONDecoder().decode(ErrorResponse.self, from: data) {
                throw NetworkError.serverError(400, errorData.error)
            }
            throw NetworkError.serverError(400, "Bad Request")
            
        case 401:
            throw NetworkError.unauthorized
            
        case 404:
            throw NetworkError.serverError(404, "リソースが見つかりません")
            
        case 429:
            let retryAfter = httpResponse.value(forHTTPHeaderField: "Retry-After")
                .flatMap { Double($0) }
            throw NetworkError.rateLimited(retryAfter: retryAfter)
            
        case 500...599:
            throw NetworkError.serverError(httpResponse.statusCode, "サーバー内部エラー")
            
        default:
            throw NetworkError.serverError(httpResponse.statusCode, "予期しないエラー")
        }
    }
    
    // MARK: - ネットワーク接続監視
    
    @available(iOS 12.0, *)
    func checkNetworkConnectivity() async -> Bool {
        let monitor = NWPathMonitor()
        let queue = DispatchQueue(label: "network_monitor")
        
        return await withCheckedContinuation { continuation in
            monitor.pathUpdateHandler = { path in
                let isConnected = path.status == .satisfied
                monitor.cancel()
                continuation.resume(returning: isConnected)
            }
            monitor.start(queue: queue)
        }
    }
}

// 使用例
@MainActor
class ViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false
    @Published var errorMessage: String?
    
    private let networkManager = RobustNetworkManager()
    
    func loadUsers() async {
        isLoading = true
        errorMessage = nil
        
        do {
            guard let url = URL(string: "https://api.example.com/users") else {
                throw NetworkError.invalidURL
            }
            
            users = try await networkManager.fetchWithRetry(
                url: url,
                type: [User].self,
                maxRetries: 3
            )
            
        } catch let error as NetworkError {
            errorMessage = error.localizedDescription
        } catch {
            errorMessage = "予期しないエラー: \(error.localizedDescription)"
        }
        
        isLoading = false
    }
}

ファイル操作とアップロード・ダウンロード

import Foundation

class FileTransferManager {
    private let session: URLSession
    
    init() {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForResource = 600 // 10分
        self.session = URLSession(configuration: config)
    }
    
    // MARK: - ファイルダウンロード
    
    func downloadFile(from url: URL, to destinationURL: URL) async throws {
        let (tempURL, response) = try await session.download(from: url)
        
        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        
        // 一時ファイルを目的の場所に移動
        try FileManager.default.moveItem(at: tempURL, to: destinationURL)
        
        print("ファイルダウンロード完了: \(destinationURL.path)")
    }
    
    // 進捗付きダウンロード(URLSessionDownloadDelegate使用)
    func downloadWithProgress(from url: URL) -> AsyncThrowingStream<DownloadProgress, Error> {
        return AsyncThrowingStream { continuation in
            let task = session.downloadTask(with: url) { tempURL, response, error in
                if let error = error {
                    continuation.finish(throwing: error)
                    return
                }
                
                guard let tempURL = tempURL,
                      let httpResponse = response as? HTTPURLResponse,
                      httpResponse.statusCode == 200 else {
                    continuation.finish(throwing: URLError(.badServerResponse))
                    return
                }
                
                // 最終進捗
                continuation.yield(DownloadProgress(
                    bytesWritten: httpResponse.expectedContentLength,
                    totalBytes: httpResponse.expectedContentLength,
                    progress: 1.0,
                    tempURL: tempURL
                ))
                
                continuation.finish()
            }
            
            task.resume()
        }
    }
    
    // MARK: - ファイルアップロード
    
    func uploadFile(fileURL: URL, to uploadURL: URL) async throws -> UploadResponse {
        var request = URLRequest(url: uploadURL)
        request.httpMethod = "POST"
        request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
        request.setValue("Bearer your-token", forHTTPHeaderField: "Authorization")
        
        let (data, response) = try await session.upload(for: request, fromFile: fileURL)
        
        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 || httpResponse.statusCode == 201 else {
            throw URLError(.badServerResponse)
        }
        
        return try JSONDecoder().decode(UploadResponse.self, from: data)
    }
    
    // マルチパートフォームデータアップロード
    func uploadMultipartForm(
        fileURL: URL,
        fields: [String: String],
        to uploadURL: URL
    ) async throws -> UploadResponse {
        
        let boundary = UUID().uuidString
        var request = URLRequest(url: uploadURL)
        request.httpMethod = "POST"
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        request.setValue("Bearer your-token", forHTTPHeaderField: "Authorization")
        
        let multipartData = createMultipartData(
            fileURL: fileURL,
            fields: fields,
            boundary: boundary
        )
        
        let (data, response) = try await session.upload(for: request, from: multipartData)
        
        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 || httpResponse.statusCode == 201 else {
            throw URLError(.badServerResponse)
        }
        
        return try JSONDecoder().decode(UploadResponse.self, from: data)
    }
    
    private func createMultipartData(
        fileURL: URL,
        fields: [String: String],
        boundary: String
    ) -> Data {
        var data = Data()
        
        // フィールドデータの追加
        for (key, value) in fields {
            data.append("--\(boundary)\r\n".data(using: .utf8)!)
            data.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
            data.append("\(value)\r\n".data(using: .utf8)!)
        }
        
        // ファイルデータの追加
        let filename = fileURL.lastPathComponent
        let mimeType = mimeTypeForFile(url: fileURL)
        
        data.append("--\(boundary)\r\n".data(using: .utf8)!)
        data.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n".data(using: .utf8)!)
        data.append("Content-Type: \(mimeType)\r\n\r\n".data(using: .utf8)!)
        
        if let fileData = try? Data(contentsOf: fileURL) {
            data.append(fileData)
        }
        
        data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
        
        return data
    }
    
    private func mimeTypeForFile(url: URL) -> String {
        let pathExtension = url.pathExtension.lowercased()
        
        switch pathExtension {
        case "jpg", "jpeg":
            return "image/jpeg"
        case "png":
            return "image/png"
        case "pdf":
            return "application/pdf"
        case "txt":
            return "text/plain"
        case "json":
            return "application/json"
        default:
            return "application/octet-stream"
        }
    }
    
    // MARK: - バックグラウンドダウンロード
    
    func backgroundDownload(from url: URL) -> URLSessionDownloadTask {
        let backgroundConfig = URLSessionConfiguration.background(
            withIdentifier: "com.myapp.background-download"
        )
        let backgroundSession = URLSession(configuration: backgroundConfig)
        
        let task = backgroundSession.downloadTask(with: url)
        task.resume()
        
        return task
    }
}

// 関連する型定義
struct DownloadProgress {
    let bytesWritten: Int64
    let totalBytes: Int64
    let progress: Double
    let tempURL: URL?
}

struct UploadResponse: Codable {
    let fileId: String
    let filename: String
    let size: Int64
    let url: String
}

// SwiftUIでの使用例
struct FileDownloadView: View {
    @State private var downloadProgress: Double = 0
    @State private var isDownloading = false
    
    private let fileManager = FileTransferManager()
    
    var body: some View {
        VStack {
            if isDownloading {
                ProgressView(value: downloadProgress, total: 1.0)
                    .progressViewStyle(LinearProgressViewStyle())
                Text("ダウンロード中: \(Int(downloadProgress * 100))%")
            } else {
                Button("ファイルをダウンロード") {
                    Task {
                        await startDownload()
                    }
                }
            }
        }
    }
    
    private func startDownload() async {
        isDownloading = true
        
        guard let url = URL(string: "https://api.example.com/files/large-file.zip") else {
            return
        }
        
        do {
            let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            let destinationURL = documentsPath.appendingPathComponent("downloaded-file.zip")
            
            try await fileManager.downloadFile(from: url, to: destinationURL)
            
        } catch {
            print("ダウンロードエラー: \(error)")
        }
        
        isDownloading = false
    }
}

Combineフレームワーク統合と実用的な活用例

import Foundation
import Combine
import SwiftUI

// MARK: - Combine Publisher拡張

extension URLSession {
    
    // 汎用APIリクエストPublisher
    func apiRequest<T: Codable>(
        for request: URLRequest,
        responseType: T.Type
    ) -> AnyPublisher<T, NetworkError> {
        
        return dataTaskPublisher(for: request)
            .tryMap { data, response -> T in
                guard let httpResponse = response as? HTTPURLResponse else {
                    throw NetworkError.serverError(0, "無効なレスポンス")
                }
                
                guard 200...299 ~= httpResponse.statusCode else {
                    throw NetworkError.serverError(httpResponse.statusCode, nil)
                }
                
                do {
                    return try JSONDecoder().decode(T.self, from: data)
                } catch {
                    throw NetworkError.decodingError(error)
                }
            }
            .mapError { error in
                if let networkError = error as? NetworkError {
                    return networkError
                } else {
                    return NetworkError.networkUnavailable
                }
            }
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
}

// MARK: - CombineベースAPIクライアント

class CombineAPIClient: ObservableObject {
    private let session = URLSession.shared
    private var cancellables = Set<AnyCancellable>()
    
    @Published var users: [User] = []
    @Published var isLoading = false
    @Published var errorMessage: String?
    
    // ユーザー一覧取得
    func fetchUsers() {
        guard let url = URL(string: "https://api.example.com/users") else {
            errorMessage = "無効なURL"
            return
        }
        
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        
        isLoading = true
        errorMessage = nil
        
        session.apiRequest(for: request, responseType: [User].self)
            .sink(
                receiveCompletion: { [weak self] completion in
                    self?.isLoading = false
                    
                    if case .failure(let error) = completion {
                        self?.errorMessage = error.localizedDescription
                    }
                },
                receiveValue: { [weak self] users in
                    self?.users = users
                }
            )
            .store(in: &cancellables)
    }
    
    // リアクティブなユーザー検索
    func searchUsers(query: String) -> AnyPublisher<[User], NetworkError> {
        guard !query.isEmpty,
              let url = URL(string: "https://api.example.com/users/search") else {
            return Just([])
                .setFailureType(to: NetworkError.self)
                .eraseToAnyPublisher()
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        let searchData = ["query": query]
        request.httpBody = try? JSONSerialization.data(withJSONObject: searchData)
        
        return session.apiRequest(for: request, responseType: [User].self)
            .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
            .removeDuplicates { $0.count == $1.count }
            .eraseToAnyPublisher()
    }
    
    // バッチリクエスト処理
    func fetchUserDetails(ids: [Int]) {
        let publishers = ids.map { id in
            fetchUserDetail(id: id)
                .catch { _ in Just(nil) }
        }
        
        Publishers.MergeMany(publishers)
            .collect()
            .sink { userDetails in
                let validUsers = userDetails.compactMap { $0 }
                print("取得したユーザー詳細: \(validUsers.count)件")
            }
            .store(in: &cancellables)
    }
    
    private func fetchUserDetail(id: Int) -> AnyPublisher<User?, Never> {
        guard let url = URL(string: "https://api.example.com/users/\(id)") else {
            return Just(nil).eraseToAnyPublisher()
        }
        
        let request = URLRequest(url: url)
        
        return session.apiRequest(for: request, responseType: User.self)
            .map { Optional($0) }
            .replaceError(with: nil)
            .eraseToAnyPublisher()
    }
}

// MARK: - SwiftUIとの統合

struct UserListView: View {
    @StateObject private var apiClient = CombineAPIClient()
    @State private var searchText = ""
    @State private var searchResults: [User] = []
    
    var body: some View {
        NavigationView {
            VStack {
                // 検索バー
                SearchBar(text: $searchText)
                    .onChange(of: searchText) { query in
                        searchUsers(query: query)
                    }
                
                // ユーザーリスト
                if apiClient.isLoading {
                    ProgressView("読み込み中...")
                } else if !searchResults.isEmpty {
                    List(searchResults, id: \.id) { user in
                        UserRowView(user: user)
                    }
                } else {
                    List(apiClient.users, id: \.id) { user in
                        UserRowView(user: user)
                    }
                }
                
                Spacer()
            }
            .navigationTitle("ユーザー一覧")
            .onAppear {
                apiClient.fetchUsers()
            }
            .alert("エラー", isPresented: .constant(apiClient.errorMessage != nil)) {
                Button("OK") {
                    apiClient.errorMessage = nil
                }
            } message: {
                Text(apiClient.errorMessage ?? "")
            }
        }
    }
    
    private func searchUsers(query: String) {
        apiClient.searchUsers(query: query)
            .receive(on: DispatchQueue.main)
            .sink(
                receiveCompletion: { _ in },
                receiveValue: { users in
                    searchResults = users
                }
            )
            .store(in: &apiClient.cancellables)
    }
}

struct UserRowView: View {
    let user: User
    
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(user.name)
                    .font(.headline)
                Text(user.email)
                    .font(.caption)
                    .foregroundColor(.secondary)
            }
            
            Spacer()
            
            if let age = user.age {
                Text("\(age)歳")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }
        }
        .padding(.vertical, 4)
    }
}

struct SearchBar: View {
    @Binding var text: String
    
    var body: some View {
        HStack {
            Image(systemName: "magnifyingglass")
                .foregroundColor(.secondary)
            
            TextField("ユーザーを検索...", text: $text)
                .textFieldStyle(RoundedBorderTextFieldStyle())
        }
        .padding(.horizontal)
    }
}

// MARK: - WebSocket通信例

@available(iOS 13.0, *)
class WebSocketManager: NSObject, ObservableObject {
    @Published var connectionStatus: WebSocketConnectionStatus = .disconnected
    @Published var receivedMessages: [String] = []
    
    private var webSocketTask: URLSessionWebSocketTask?
    private let session = URLSession(configuration: .default)
    
    func connect(to url: URL) {
        webSocketTask = session.webSocketTask(with: url)
        webSocketTask?.resume()
        
        connectionStatus = .connecting
        
        // メッセージ受信の開始
        receiveMessage()
        
        // 接続状態の監視
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.connectionStatus = .connected
        }
    }
    
    func sendMessage(_ text: String) {
        let message = URLSessionWebSocketTask.Message.string(text)
        webSocketTask?.send(message) { error in
            if let error = error {
                print("WebSocket送信エラー: \(error)")
            }
        }
    }
    
    func disconnect() {
        webSocketTask?.cancel(with: .normalClosure, reason: nil)
        connectionStatus = .disconnected
    }
    
    private func receiveMessage() {
        webSocketTask?.receive { [weak self] result in
            switch result {
            case .success(let message):
                switch message {
                case .string(let text):
                    DispatchQueue.main.async {
                        self?.receivedMessages.append(text)
                    }
                case .data(let data):
                    print("受信データ: \(data)")
                @unknown default:
                    break
                }
                
                // 次のメッセージの受信
                self?.receiveMessage()
                
            case .failure(let error):
                print("WebSocket受信エラー: \(error)")
                DispatchQueue.main.async {
                    self?.connectionStatus = .disconnected
                }
            }
        }
    }
}

enum WebSocketConnectionStatus {
    case disconnected
    case connecting
    case connected
}