This commit is contained in:
Ho Ngoc Hai
2026-05-23 18:37:02 +07:00
parent f15d91ee29
commit 76d75c753b
3993 changed files with 403 additions and 0 deletions

View File

@@ -0,0 +1,254 @@
---
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 loi li 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 thc 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
/// Dch 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
/// Thc hin 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)