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() | 応答を 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、または 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 | リクエスト中に接続が切断されました |
すべての正規化されたエラーは、メッセージ "Unable to connect to the server" を返します。
非同期コードでエラーをキャッチします:
do {
let result = try await Mesh()
.setUrlPath("/api")
.request(of: Response.self)
} catch let error as NSError {
print("Error \(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 |
.setDestination(_:) | DownloadRequest.Destination? | カスタムダウンロード先 |
.setResumeData(_:) | Data? | 再開ダウンロード用の再開データ |
アップロード設定
| メソッド | 型 | 説明 |
|---|---|---|
.setUploadType(_:) | UploadType | .file、.data、.stream、.multipart |
.setFileURL(_:) | URL? | .file タイプ用のファイル URL |
.setFileData(_:) | Data? | .data タイプ用のファイルデータ |
.setStream(_:) | InputStream? | .stream タイプ用のストリーム |
.setMimeType(_:) | String? | アップロードの MIME タイプ |
.setFileName(_:) | String? | サーバーに送信するファイル名 |
.setAddformData(name:fileName:fileData:mimeType:) | - | マルチパートフォームデータを追加 |
.setUploadDatas(_:) | [MultipleUpload] | マルチパート用のプリビルドアップロードデータ配列 |
ベストプラクティス
- グローバル設定を使用: アプリの起動時に
Mesh.setHeaders()、Mesh.setUrlHost()、Mesh.setParameters()を使用してベース設定を設定します。 - キーパス抽出で冗長性を削減:
modelKeyPathを使用して、大きな JSON 構造全体に対して中間モデルを作成する必要をなくします。 - 復元力のあるラッパーを活用: 不安定な API では、
@Default、@IgnoreError、@ConvertTo*ラッパーを使用して、予期しない変更によるデコードの失敗を防ぎます。 - リトライポリシーを実装: 不安定なネットワーク環境では、
RetryPolicyを使用してリクエストの信頼性を向上させます。 - 適切なエラーハンドリング: 常に
try await呼び出しを do-catch ブロックでラップして、ネットワーク関連のエラーをユーザーに優雅に伝えます。 - Combine で SwiftUI と統合:
ObservableObjectパターンを使用して、ネットワーク状態を SwiftUI ビューに簡単にバインドします。