--- name: swift-networking description: HTTP client, API handling, Error management cho Swift Enterprise. Use for REST APIs, URLSession, async networking, hoặc khi cần structured API layer. compatibility: "Swift 5.9+, iOS 17+, Foundation" metadata: author: Velik Ho version: "1.0" references: "URLSession, Swift Concurrency" --- # Swift Networking Patterns HTTP client và API handling patterns cho Swift Enterprise applications. ## When to Use This Skill / Khi Nào Sử Dụng Use this skill when: - Building REST API client / Xây dựng REST API client - Need URLSession wrapper / Cần wrapper cho URLSession - Implementing token-based auth / Triển khai auth token-based - Error handling for APIs / Xử lý lỗi API ## Core Patterns / Mẫu Chính ### API Error Enum ```swift // Services/APIError.swift /// API error types /// Các loại lỗi API enum APIError: Error, LocalizedError { case invalidURL case noData case decodingError(Error) case networkError(Error) case serverError(statusCode: Int, message: String?) case unauthorized case forbidden case notFound case rateLimited case unknown var errorDescription: String? { switch self { case .invalidURL: return "Invalid URL / URL không hợp lệ" case .noData: return "No data received / Không nhận được dữ liệu" case .decodingError(let error): return "Decoding error: \(error.localizedDescription)" case .networkError(let error): return "Network error: \(error.localizedDescription)" case .serverError(let code, let message): return "Server error (\(code)): \(message ?? "Unknown")" case .unauthorized: return "Unauthorized / Chưa xác thực" case .forbidden: return "Access forbidden / Truy cập bị từ chối" case .notFound: return "Resource not found / Không tìm thấy" case .rateLimited: return "Rate limited / Giới hạn request" case .unknown: return "Unknown error / Lỗi không xác định" } } } ``` ### HTTP Method Enum ```swift /// HTTP request methods /// Các phương thức HTTP enum HTTPMethod: String { case get = "GET" case post = "POST" case put = "PUT" case patch = "PATCH" case delete = "DELETE" } ``` ### API Service Protocol ```swift /// API service protocol for DI /// Protocol API service cho DI protocol APIServiceProtocol { func request( endpoint: String, method: HTTPMethod, body: Encodable?, headers: [String: String]? ) async throws -> T } ``` ### API Service Implementation ```swift // Services/APIService.swift /// Main API service /// Dịch vụ API chính final class APIService: APIServiceProtocol { // MARK: - Singleton static let shared = APIService() // MARK: - Properties private let session: URLSession private let encoder: JSONEncoder private let decoder: JSONDecoder // MARK: - Init init(session: URLSession = .shared) { self.session = session self.encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase encoder.dateEncodingStrategy = .iso8601 self.decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.dateDecodingStrategy = .iso8601 } // MARK: - Request /// Perform network request /// Thực hiện request network func request( endpoint: String, method: HTTPMethod = .get, body: Encodable? = nil, headers: [String: String]? = nil ) async throws -> T { // Build URL guard let url = URL(string: APIConfig.baseURL + APIConfig.apiVersion + endpoint) else { throw APIError.invalidURL } // Create request var request = URLRequest(url: url) request.httpMethod = method.rawValue request.timeoutInterval = APIConfig.timeout // Set headers request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("application/json", forHTTPHeaderField: "Accept") // Add auth token if let token = await AuthManager.shared.accessToken { request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") } // Add custom headers headers?.forEach { key, value in request.setValue(value, forHTTPHeaderField: key) } // Set body if let body = body { request.httpBody = try encoder.encode(body) } // Perform request let (data, response) = try await session.data(for: request) // Handle response guard let httpResponse = response as? HTTPURLResponse else { throw APIError.unknown } // Check status code switch httpResponse.statusCode { case 200...299: do { return try decoder.decode(T.self, from: data) } catch { throw APIError.decodingError(error) } case 401: await AuthManager.shared.handleUnauthorized() throw APIError.unauthorized case 403: throw APIError.forbidden case 404: throw APIError.notFound case 429: throw APIError.rateLimited default: let message = String(data: data, encoding: .utf8) throw APIError.serverError(statusCode: httpResponse.statusCode, message: message) } } // MARK: - Convenience Methods /// GET request func get(endpoint: String) async throws -> T { try await request(endpoint: endpoint, method: .get, body: nil as String?, headers: nil) } /// POST request func post(endpoint: String, body: B) async throws -> T { try await request(endpoint: endpoint, method: .post, body: body, headers: nil) } /// PUT request func put(endpoint: String, body: B) async throws -> T { try await request(endpoint: endpoint, method: .put, body: body, headers: nil) } /// DELETE request func delete(endpoint: String) async throws -> T { try await request(endpoint: endpoint, method: .delete, body: nil as String?, headers: nil) } } ``` --- ## Common Mistakes / Lỗi Thường Gặp | Mistake | Problem | Solution | |---------|---------|----------| | No error handling | Crashes | Use `do-catch` with custom errors | | Force unwrap URLs | Crashes | Guard with `APIError.invalidURL` | | Blocking main thread | Frozen UI | Use `async/await` | | No timeout | Hanging requests | Set `timeoutInterval` | | Hardcoded URLs | Hard to maintain | Use `APIConfig` enum | ## Quick Reference / Tham Chiếu Nhanh | Category | Standard | |----------|----------| | HTTP Client | URLSession with async/await | | Errors | Custom `APIError` enum | | Encoding | snake_case JSON | | Auth | Bearer token injection | | Timeout | 30 seconds default | ## Resources / Tài Nguyên - [Swift Enterprise Architect](../swift-enterprise-architect/SKILL.md) - Architecture - [Swift Security](../swift-security/SKILL.md) - Token management - [Apple URLSession](https://developer.apple.com/documentation/foundation/urlsession)