SwiftMesh AI 技能
關於使用 SwiftMesh 的全面 AI 參考指南 — 一個 Alamofire + Codable 的包裝器,支援 async/await、Combine、流暢配置、檔案上傳/下載、JSON 鍵路徑解析、彈性的 Codable 包裝器與內建日誌。
快速參考卡
| 功能 | 方法 | 描述 |
|---|---|---|
| GET 請求 | .request(of: Model.self) | 將回應解碼為 Codable 模型 |
| 鍵路徑 | .request(of: Model.self, modelKeyPath: "data.user") | 提取巢狀 JSON |
| 檔案上傳 | .upload(of: Result.self) | 上傳檔案/Data/串流/多部分 |
| 檔案下載 | .download() | 下載或續傳下載 |
| 原始資料 | .requestData() | 取得原始回應 Data |
| 原始字串 | .requestString() | 將回應以字串形式取得 |
| 重試策略 | .setInterceptor(RetryPolicy()) | 自動重試並帶有回退機制 |
| 日誌 | Mesh.enableLog() | 啟用網路日誌 |
核心架構
SwiftMesh 在 Mesh 類別上使用建構者模式。每個配置方法都回傳 Self,從而實現流暢的鏈式呼叫。流程如下:
Configure (Mesh + Config) → Execute (Request/Upload/Download) → Handle (Handle)
檔案結構
| 檔案 | 用途 |
|---|---|
Mesh.swift | 包含所有屬性的核心建構者類別 |
Config.swift | 流暢的鏈式設定器 + 全域配置 |
Request.swift | async/await 請求執行 |
Handle.swift | URL 建構、錯誤處理、回應處理、RetryPolicy |
Upload.swift | 檔案上傳(檔案、資料、串流、多部分) |
Download.swift | 檔案下載(標準、可續傳) |
KeyPath.swift | 用於巢狀提取的 JSON 鍵路徑解碼器 |
Codable+.swift | 彈性的屬性包裝器(@Default, @IgnoreError, @ConvertTo*) |
Log.swift | 網路記錄器(cURL、狀態、計時、JSON) |
使用模式
1. 基本 GET 請求
let result = try await Mesh()
.setRequestMethod(.get)
.setUrlHost("https://api.example.com")
.setUrlPath("/weather/city/101030100")
.request(of: Weather.self)
2. 帶有 JSON 鍵路徑提取的 GET 請求
僅提取 JSON 回應的巢狀部分,而無需解析整個結構:
// JSON: { "code": 200, "data": { "yesterday": { "temp": 25, "notice": "Sunny" } } }
let yesterday = try await Mesh()
.setRequestMethod(.get)
.setUrlHost("https://api.example.com")
.setUrlPath("/weather")
.request(of: Forecast.self, modelKeyPath: "data.yesterday")
3. 帶有參數的 POST 請求
let result = try await Mesh()
.setRequestMethod(.post)
.setUrlHost("https://api.example.com")
.setUrlPath("/login")
.setParameters(["username": "admin", "password": "123456"])
.request(of: LoginResult.self)
4. 帶有 JSON 編碼的 POST 請求
let result = try await Mesh()
.setRequestMethod(.post)
.setUrlHost("https://api.example.com")
.setUrlPath("/api/data")
.setRequestEncoding(JSONEncoding.default)
.setParameters(["key": "value"])
.request(of: Response.self)
5. 帶有自定義標頭的請求
let result = try await Mesh()
.setUrlHost("https://api.example.com")
.setUrlPath("/secure/data")
.setHeads(["Authorization": "Bearer token123"])
.request(of: SecureData.self)
6. 帶有重試策略的請求
let result = try await Mesh()
.setUrlHost("https://api.example.com")
.setUrlPath("/unstable-api")
.setInterceptor(RetryPolicy(maxRetryCount: 3))
.request(of: Data.self)
7. 基於 URLRequest 的請求
當你已經有一個 URLRequestConvertible 時:
let urlRequest = try URLRequest(url: URL(string: "https://api.example.com/data")!, method: .get)
let result = try await Mesh()
.urlRequest(urlRequest, type: Response.self)
8. 原始資料回應
let data = try await Mesh()
.setRequestMethod(.get)
.setUrlHost("https://api.example.com")
.setUrlPath("/raw")
.requestData()
9. 原始字串回應
let string = try await Mesh()
.setRequestMethod(.get)
.setUrlHost("https://api.example.com")
.setUrlPath("/text")
.requestString()
10. 檔案下載
let fileURL = try await Mesh()
.setRequestMethod(.get)
.setUrlHost("https://example.com")
.setUrlPath("/files/document.pdf")
.setDestination { _, _ in
let dest = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
.appendingPathComponent("document.pdf")
return (dest, [.removePreviousFile, .createIntermediateDirectories])
}
.download()
11. 可續傳下載
let fileURL = try await Mesh()
.setUrlHost("https://example.com")
.setUrlPath("/files/large-file.zip")
.setDownloadType(.resume)
.setResumeData(savedResumeData) // 來自先前中斷的下載
.download()
12. 單一檔案上傳 (URL)
let result = try await Mesh()
.setRequestMethod(.post)
.setUrlHost("https://api.example.com")
.setUrlPath("/upload")
.setUploadType(.file)
.setFileURL(fileURL)
.upload(of: UploadResult.self)
13. 單一檔案上傳 (Data)
let result = try await Mesh()
.setRequestMethod(.post)
.setUrlHost("https://api.example.com")
.setUrlPath("/upload")
.setUploadType(.data)
.setFileData(imageData)
.upload(of: UploadResult.self)
14. 多部分表單上傳
let result = try await Mesh()
.setRequestMethod(.post)
.setUrlHost("https://api.example.com")
.setUrlPath("/upload/multi")
.setUploadType(.multipart)
.setAddformData(name: "file",
fileName: "photo.jpg",
fileData: imageData,
mimeType: "image/jpeg")
.setAddformData(name: "description",
fileData: "My photo".data(using: .utf8))
.upload(of: UploadResult.self)
15. 使用預先構建的 UploadDatas 進行多部分上傳
let uploads = [
MultipleUpload.formData(name: "file1", fileName: "a.jpg", fileData: data1, mimeType: "image/jpeg"),
MultipleUpload.formData(name: "file2", fileName: "b.pdf", fileURL: fileURL)
]
let result = try await Mesh()
.setRequestMethod(.post)
.setUrlHost("https://api.example.com")
.setUrlPath("/upload/batch")
.setUploadType(.multipart)
.setUploadDatas(uploads)
.setParameters(["userId": "123"]) // 額外的表單欄位
.upload(of: BatchResult.self)
16. 自定義超時
let result = try await Mesh()
.setUrlHost("https://api.example.com")
.setUrlPath("/slow-api")
.setTimeout(60) // 60 秒
.request(of: Response.self)
全域配置
在應用程式啟動時設定一次(例如在 AppDelegate 中):
// 啟用網路日誌
Mesh.enableLog(.log) // 或 .print
// 設定全域預設標頭
Mesh.setHeaders(["Authorization": "Bearer token", "App-Version": "1.0"])
// 設定全域預設參數
Mesh.setParameters(["platform": "ios", "sdk_version": "2.0"])
// 設定全域 URL 主機
Mesh.setUrlHost("https://api.example.com")
那麼單次請求的配置只需要路徑:
// 使用全域標頭、參數和 urlHost
let result = try await Mesh()
.setUrlPath("/weather")
.request(of: Weather.self)
彈性的 Codable 屬性包裝器
優雅地處理不一致的 API 回應,而不會導致解碼失敗。
@Default 包裝器
當欄位缺失或無效時提供預設值:
struct Response: Codable {
@Default.True var isEnabled: Bool // 缺失 → true
@Default.False var isDeleted: Bool // 缺失 → false
@Default.EmptyString var name: String // 缺失 → ""
@Default.EmptyInt var count: Int // 缺失 → 0
@Default.EmptyArray var tags: [String] // 缺失 → []
@Default.EmptyDictionary var meta: [String: Int] // 缺失 → [:]
@Default.Now var createdAt: Date // 缺失 → Date()
}
@IgnoreError
當欄位解碼失敗時回傳 nil 而不是拋出錯誤:
struct Response: Codable {
@IgnoreError var description: String? // 無效類型 → nil(不會崩潰)
@IgnoreError var nested: NestedModel? // 缺失/格式錯誤 → nil
}
@ConvertToString
從 JSON 中接收 String、Int 或 Double → 轉換為 String?:
struct Response: Codable {
@ConvertToString var version: String? // "1.0", 1, 或 1.0 → "1.0", "1", "1.0"
}
@ConvertToInt
從 JSON 中接收 Int、String 或 Double → 轉換為 Int?:
struct Response: Codable {
@ConvertToInt var count: Int? // 42, "42", 或 42.9 → 42, 42, 42
}
@ConvertToDouble
從 JSON 中接收 Double、Int、Float 或 String → 轉換為 Double?:
struct Response: Codable {
@ConvertToDouble var price: Double? // 9.99, 10, "9.99" → 9.99, 10.0, 9.99
}
@ConvertToFloat
從 JSON 中接收 Float、Int、Double 或 String → 轉換為 Float?:
struct Response: Codable {
@ConvertToFloat var rating: Float? // 4.5, 5, "4.5" → 4.5, 5.0, 4.5
}
JSON 鍵路徑解碼器
無需解析整個回應即可提取巢狀 JSON 值:
// JSON: { "code": 200, "data": { "list": [{ "id": 1 }, { "id": 2 }] } }
// 提取單個巢狀物件
let firstItem = try await Mesh()
.setRequestMethod(.get)
.setUrlPath("/items")
.request(of: Item.self, modelKeyPath: "data.list.0")
// KeyPath 解碼器也可以直接使用:
let decoder = JSONDecoder.default
let item = try decoder.decode(Item.self, from: jsonData, keyPath: "data.list.0")
let items = try decoder.decodeArray([Item].self, from: jsonData, keyPath: "data.list")
預設解碼器會自動配置:
keyDecodingStrategy = .convertFromSnakeCase(snake_case → camelCase)dateDecodingStrategy = .iso8601
Combine + SwiftUI 整合
ObservableObject 模式
class RequestModel: ObservableObject {
@MainActor @Published var weather: Weather?
@MainActor @Published var errorMessage: String?
func fetchWeather() {
Task {
do {
let result = try await Mesh()
.setRequestMethod(.get)
.setUrlHost("https://api.example.com")
.setUrlPath("/weather")
.request(of: Weather.self)
await MainActor.run { self.weather = result }
} catch {
await MainActor.run { self.errorMessage = error.localizedDescription }
}
}
}
}
SwiftUI 使用方式
struct WeatherView: View {
@StateObject private var model = RequestModel()
var body: some View {
VStack {
if let weather = model.weather {
Text("\(weather.temperature)°C")
}
if let error = model.errorMessage {
Text(error).foregroundColor(.red)
}
}
.onAppear { model.fetchWeather() }
}
}
UIKit + Combine 使用方式
class ViewController: UIViewController {
private var model = RequestModel()
private var cancellables: Set<AnyCancellable> = []
override func viewDidLoad() {
super.viewDidLoad()
model.$weather
.receive(on: RunLoop.main)
.sink { weather in
print("Weather: \(String(describing: weather))")
}
.store(in: &cancellables)
model.fetchWeather()
}
}
錯誤處理
SwiftMesh 將常見的網路錯誤標準化為使用者友善的 NSError:
| 錯誤代碼 | 描述 |
|---|---|
NSURLErrorNotConnectedToInternet | 無網路連線 |
NSURLErrorTimedOut | 請求逾時 |
NSURLErrorCannotFindHost | 找不到主機 |
NSURLErrorCannotConnectToHost | 無法連線到主機 |
NSURLErrorNetworkConnectionLost | 請求過程中連線中斷 |
所有標準化後的錯誤都會回傳訊息:"無法連線到伺服器"
在非同步程式碼中捕獲錯誤:
do {
let result = try await Mesh()
.setUrlPath("/api")
.request(of: Response.self)
} catch let error as NSError {
print("錯誤 \(error.code): \(error.localizedDescription)")
}
RetryPolicy
內建帶有線性回退的重試機制(1秒、2秒、3秒...):
// 預設:3 次重試
let policy = RetryPolicy()
// 自定義:5 次重試
let policy = RetryPolicy(maxRetryCount: 5)
// 與請求一起使用
let result = try await Mesh()
.setUrlPath("/unstable-api")
.setInterceptor(policy)
.request(of: Response.self)
日誌
在應用程式啟動時啟用:
// 使用 Apple 的統一 Logger (os.log)
Mesh.enableLog(.log)
// 使用 Swift print()
Mesh.enableLog(.print)
輸出包含:
- cURL 命令(便於重現)
- HTTP 狀態碼
- 耗時
- 美化列印的 JSON 回應
配置方法參考
所有方法都是可鏈式的(回傳 Self):
基本配置
| 方法 | 類型 | 描述 |
|---|---|---|
.setTimeout(_:) | TimeInterval | 請求逾時秒數(預設:15) |
.setInterceptor(_:) | RequestInterceptor? | 重試策略 / 憑證處理器 |
.setRequestMethod(_:) | HTTPMethod | GET、POST、PUT、DELETE 等 |
.setHeads(_:) | [String: String] | 每次請求的標頭 |
.setRequestEncoding(_:) | ParameterEncoding | URLEncoding、JSONEncoding 等 |
.setUrlHost(_:) | String? | 每次請求的 URL 主機 |
.setUrlPath(_:) | String? | URL 路徑 |
.setParameters(_:) | [String: Any]? | 請求參數 |
下載配置
| 方法 | 類型 | 描述 |
|---|---|---|
.setDownloadType(_:) | DownloadType | .download 或 .resume |
...
[為翻譯而截斷]