SwiftMesh AI 技能
一份全面的 AI 参考文档,用于使用 SwiftMesh —— 一个基于 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() | 获取响应 String |
| 重试策略 | .setInterceptor(RetryPolicy()) | 带退避的自动重试 |
| 日志 | Mesh.enableLog() | 启用网络日志 |
核心架构
SwiftMesh 在 Mesh 类上使用构建者模式。每个配置方法都返回 Self,从而实现流畅的链式调用。流程如下:
配置 (Mesh + Config) → 执行 (Request/Upload/Download) → 处理 (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, or 1.0 → "1.0", "1", "1.0"
}
@ConvertToInt
接受 JSON 中的 Int、String 或 Double → 转换为 Int?:
struct Response: Codable {
@ConvertToInt var count: Int? // 42, "42", or 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(蛇形命名法 → 驼峰命名法)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 \(error.code): \(error.localizedDescription)")
}
RetryPolicy
内置线性退避重试(1s, 2s, 3s...):
// 默认: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 |
.setDestination(_:) | DownloadRequest.Destination? | 自定义文件目标路径 |
.setResumeData(_:) | Data? | 用于恢复下载的数据 |
上传配置
| 方法 | 类型 | 描述 |
|---|---|---|
.setUploadType(_:) | UploadType | .file, .data, .stream, .multipart |
.setFileURL(_:) | URL? | 要上传的文件的 URL |
.setFileData(_:) | Data? | 要上传的数据 |
.setStreamURL(_:) | URL? | 要上传的流 URL |
.setUploadDatas(_:) | [MultipleUpload] | 多部分表单数据数组 |
.setAddformData(name:fileName:fileData:mimeType:) | - | 添加单个表单数据项 |
响应处理
| 方法 | 类型 | 描述 |
|---|---|---|
.request(of:) | 泛型 T: Codable | 解码为 Model |
.request(of:modelKeyPath:) | 泛型 T: Codable | 通过键路径解码 |
.requestData() | 异步 Data | 返回原始 Data |
.requestString() | 异步 String | 返回原始 String |
.upload(of:) | 泛型 T: Codable | 上传并解码响应 |
.download() | 异步 URL | 下载文件到目标位置 |
.urlRequest(_:type:) | 泛型 T: Codable | 基于 URLRequest 的请求 |
版本兼容性
| SwiftMesh 版本 | 需求 |
|---|---|
| >= 1.0.0 | Swift 5.5+ / iOS 15+ / macOS 12+ |
安装
Swift Package Manager
在 Package.swift 中添加:
dependencies: [
.package(url: "https://github.com/zjinhu/SwiftMesh.git", from: "1.0.0")
]
或者通过 Xcode 添加:
- 文件 → 添加包依赖...
- 输入仓库 URL:
https://github.com/zjinhu/SwiftMesh.git
许可证
SwiftMesh 采用 MIT 许可证发布。