Communitygithub.com

zjinhu/SwiftMesh

SwiftMesh は、Alamofire と Codable をラップした総合的な AI リファレンスです。async/await、Combine、流れるような設定、ファイルアップロード/ダウンロード、JSON キーパス解析、復元力のある Codable ラッパー、組み込みのログ機能を提供します。

SwiftMesh とは?

SwiftMesh は、iOS/macOS アプリ向けのネットワーキングラッパーであり、Alamofire と Swift の Codable プロトコルを基盤としています。このスキルは、開発者が SwiftMesh の全機能を迅速に習得できるように設計された包括的なリファレンスです。async/await 構文による非同期リクエスト、Combine フレームワークとの統合、チェーン可能なビルダーパターンによる直感的な設定、ファイルのアップロード/ダウンロード、JSON キーパスを使用した部分的なデータ抽出、不安定な API レスポンスを処理するための復元力のある Codable プロパティラッパー、そして内蔵のリトライポリシーとログ機能を備えています。SwiftMesh は、Claude Code、Coder、Codex などの AI エージェントが、iOS/macOS プロジェクトにおいてネットワーキングコードを生成または理解する際のリファレンスとして活用できます。SKILL.md は、クイックリファレンスカード、コアアーキテクチャ、豊富な使用例、グローバル設定、エラーハンドリング、Combine と SwiftUI との統合など、開発者が実際のコードを書く際にすぐに参照できる実践的なドキュメントを提供します。

対応~Claude Code~Codex CLI~Cursor
npx skills add zjinhu/SwiftMesh

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

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.swiftasync/await リクエスト実行
Handle.swiftURL 構築、エラー処理、応答処理、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(_:)HTTPMethodGET、POST、PUT、DELETE など
.setHeads(_:)[String: String]リクエストごとのヘッダー
.setRequestEncoding(_:)ParameterEncodingURLEncoding、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]マルチパート用のプリビルドアップロードデータ配列

ベストプラクティス

  1. グローバル設定を使用: アプリの起動時に Mesh.setHeaders()、Mesh.setUrlHost()、Mesh.setParameters() を使用してベース設定を設定します。
  2. キーパス抽出で冗長性を削減: modelKeyPath を使用して、大きな JSON 構造全体に対して中間モデルを作成する必要をなくします。
  3. 復元力のあるラッパーを活用: 不安定な API では、@Default、@IgnoreError、@ConvertTo* ラッパーを使用して、予期しない変更によるデコードの失敗を防ぎます。
  4. リトライポリシーを実装: 不安定なネットワーク環境では、RetryPolicy を使用してリクエストの信頼性を向上させます。
  5. 適切なエラーハンドリング: 常に try await 呼び出しを do-catch ブロックでラップして、ネットワーク関連のエラーをユーザーに優雅に伝えます。
  6. Combine で SwiftUI と統合: ObservableObject パターンを使用して、ネットワーク状態を SwiftUI ビューに簡単にバインドします。

関連スキル