---
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<T: Decodable>(
        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<T: Decodable>(
        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<T: Decodable>(endpoint: String) async throws -> T {
        try await request(endpoint: endpoint, method: .get, body: nil as String?, headers: nil)
    }
    
    /// POST request
    func post<T: Decodable, B: Encodable>(endpoint: String, body: B) async throws -> T {
        try await request(endpoint: endpoint, method: .post, body: body, headers: nil)
    }
    
    /// PUT request
    func put<T: Decodable, B: Encodable>(endpoint: String, body: B) async throws -> T {
        try await request(endpoint: endpoint, method: .put, body: body, headers: nil)
    }
    
    /// DELETE request
    func delete<T: Decodable>(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)
