Communitygithub.com

SwiftMesh

Ein umfassender KI-Assistent für SwiftMesh – einen Alamofire + Codable Wrapper mit async/await, Combine, fließender Konfiguration, Datei-Upload/Download, JSON-Schlüsselpfad-Parsing, robusten Codable-Wrappern und integriertem Logging.

Was ist SwiftMesh?

SwiftMesh ist ein KI-Skill, der eine vollständige Referenz und Anleitungen für die SwiftMesh-Bibliothek bietet, einem Alamofire + Codable Wrapper für iOS-Entwicklung. Der Skill hilft Entwicklern, Netzwerkoperationen mit async/await, Pattern-Observable-Objekten mit Combine, fließender Builder-Konfiguration, dem Herunterladen und Hochladen von Dateien, JSON-Schlüsselpfad-Parsing mit KeyPath-Decodern, robusten Codable-Wrappern wie @Default, @IgnoreError und @ConvertTo* sowie integriertem Logging zu verwalten. Er wird als schnelle Referenzkarte, detaillierte Verwendungsmuster und Architekturdiagramm geliefert und ist als Datei SKILL.md für KI-Agenten wie Claude Code, Cursor oder Codex konzipiert. Entwickler können SwiftMesh direkt in Swift-Projekte integrieren, um Netzwerkaufrufe zu vereinfachen und Fehlerbehandlung mit resilienten Codable-Wrappern zu verbessern. Der Skill unterstützt die gesamte Bandbreite von GET- und POST-Anfragen über Datei-Uploads bis hin zu fortgeschrittenen Funktionen wie Wiederaufnahme von Downloads und Retry-Policys.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add zjinhu/SwiftMesh

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

SwiftMesh KI-Skill

Eine umfassende KI-Referenz zur Verwendung von SwiftMesh – ein Alamofire + Codable Wrapper mit async/await, Combine, fließender Konfiguration, Datei-Upload/Download, JSON-Schlüsselpfad-Parsing, robusten Codable-Wrappern und integriertem Logging.


Schnellreferenzkarte

FunktionMethodeBeschreibung
GET-Anfrage.request(of: Model.self)Antwort in Codable-Modell dekodieren
Schlüsselpfad.request(of: Model.self, modelKeyPath: "data.user")Verschachteltes JSON extrahieren
Datei-Upload.upload(of: Result.self)Datei/Daten/Stream/Multipart hochladen
Datei-Download.download()Datei herunterladen oder fortsetzen
Rohdaten.requestData()Rohe Antwort-Daten erhalten
Roher String.requestString()Antwort als String erhalten
Wiederholungsrichtlinie.setInterceptor(RetryPolicy())Automatische Wiederholung mit Backoff
LoggingMesh.enableLog()Netzwerk-Logging aktivieren

Kernarchitektur

SwiftMesh verwendet ein Builder-Pattern auf der Mesh-Klasse. Jede Konfigurationsmethode gibt Self zurück, was eine fließende Verkettung ermöglicht. Der Ablauf ist:

Konfigurieren (Mesh + Config) → Ausführen (Request/Upload/Download) → Verarbeiten (Handle)

Dateistruktur

DateiZweck
Mesh.swiftKern-Builder-Klasse mit allen Eigenschaften
Config.swiftFließende verkettbare Setter + globale Konfiguration
Request.swiftasync/await-Anfrageausführung
Handle.swiftURL-Konstruktion, Fehlerbehandlung, Antwortverarbeitung, RetryPolicy
Upload.swiftDatei-Upload (Datei, Daten, Stream, Multipart)
Download.swiftDatei-Download (Standard, fortsetzbar)
KeyPath.swiftJSON-Schlüsselpfad-Decoder für verschachtelte Extraktion
Codable+.swiftRobuste Property-Wrapper (@Default, @IgnoreError, @ConvertTo*)
Log.swiftNetzwerk-Logger (cURL, Status, Timing, JSON)

Verwendungsmuster

1. Einfache GET-Anfrage

let result = try await Mesh()
    .setRequestMethod(.get)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/weather/city/101030100")
    .request(of: Weather.self)

2. GET mit JSON-Schlüsselpfad-Extraktion

Nur einen verschachtelten Teil der JSON-Antwort extrahieren, ohne die gesamte Struktur zu parsen:

// 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-Anfrage mit Parametern

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/login")
    .setParameters(["username": "admin", "password": "123456"])
    .request(of: LoginResult.self)

4. POST mit JSON-Codierung

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. Anfrage mit benutzerdefinierten Headern

let result = try await Mesh()
    .setUrlHost("https://api.example.com")
    .setUrlPath("/secure/data")
    .setHeads(["Authorization": "Bearer token123"])
    .request(of: SecureData.self)

6. Anfrage mit Wiederholungsrichtlinie

let result = try await Mesh()
    .setUrlHost("https://api.example.com")
    .setUrlPath("/unstable-api")
    .setInterceptor(RetryPolicy(maxRetryCount: 3))
    .request(of: Data.self)

7. URLRequest-basierte Anfrage

Wenn Sie bereits ein URLRequestConvertible haben:

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. Rohdaten-Antwort

let data = try await Mesh()
    .setRequestMethod(.get)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/raw")
    .requestData()

9. Roher String-Antwort

let string = try await Mesh()
    .setRequestMethod(.get)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/text")
    .requestString()

10. Datei-Download

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. Fortsetzbarer Download

let fileURL = try await Mesh()
    .setUrlHost("https://example.com")
    .setUrlPath("/files/large-file.zip")
    .setDownloadType(.resume)
    .setResumeData(savedResumeData)  // von vorherigem unterbrochenem Download
    .download()

12. Einzelner Datei-Upload (URL)

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/upload")
    .setUploadType(.file)
    .setFileURL(fileURL)
    .upload(of: UploadResult.self)

13. Einzelner Datei-Upload (Daten)

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/upload")
    .setUploadType(.data)
    .setFileData(imageData)
    .upload(of: UploadResult.self)

14. Multipart-Formular-Upload

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. Multipart mit vorgefertigten 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"])  // zusätzliche Formularfelder
    .upload(of: BatchResult.self)

16. Benutzerdefiniertes Timeout

let result = try await Mesh()
    .setUrlHost("https://api.example.com")
    .setUrlPath("/slow-api")
    .setTimeout(60)  // 60 Sekunden
    .request(of: Response.self)

Globale Konfiguration

Einmalig beim App-Start setzen (z.B. in AppDelegate):

// Netzwerk-Logging aktivieren
Mesh.enableLog(.log)  // oder .print

// Globale Standard-Header setzen
Mesh.setHeaders(["Authorization": "Bearer token", "App-Version": "1.0"])

// Globale Standard-Parameter setzen
Mesh.setParameters(["platform": "ios", "sdk_version": "2.0"])

// Globalen URL-Host setzen
Mesh.setUrlHost("https://api.example.com")

Dann benötigt die anfragenspezifische Konfiguration nur noch den Pfad:

// Verwendet globale Header, Parameter und urlHost
let result = try await Mesh()
    .setUrlPath("/weather")
    .request(of: Weather.self)

Robuste Codable Property-Wrapper

Behandeln Sie inkonsistente API-Antworten elegant ohne Decodierungsfehler.

@Default-Wrapper

Bereitstellung von Fallback-Werten, wenn Felder fehlen oder ungültig sind:

struct Response: Codable {
    @Default.True var isEnabled: Bool              // Fehlt → true
    @Default.False var isDeleted: Bool             // Fehlt → false
    @Default.EmptyString var name: String          // Fehlt → ""
    @Default.EmptyInt var count: Int               // Fehlt → 0
    @Default.EmptyArray var tags: [String]         // Fehlt → []
    @Default.EmptyDictionary var meta: [String: Int] // Fehlt → [:]
    @Default.Now var createdAt: Date               // Fehlt → Date()
}

@IgnoreError

Gibt nil zurück, anstatt einen Fehler zu werfen, wenn ein Feld nicht decodiert werden kann:

struct Response: Codable {
    @IgnoreError var description: String?   // Ungültiger Typ → nil (kein Absturz)
    @IgnoreError var nested: NestedModel?   // Fehlt/fehlerhaft → nil
}

@ConvertToString

Akzeptiert String, Int oder Double aus JSON → konvertiert zu String?:

struct Response: Codable {
    @ConvertToString var version: String?  // "1.0", 1, oder 1.0 → "1.0", "1", "1.0"
}

@ConvertToInt

Akzeptiert Int, String oder Double aus JSON → konvertiert zu Int?:

struct Response: Codable {
    @ConvertToInt var count: Int?  // 42, "42", oder 42.9 → 42, 42, 42
}

@ConvertToDouble

Akzeptiert Double, Int, Float oder String aus JSON → konvertiert zu Double?:

struct Response: Codable {
    @ConvertToDouble var price: Double?  // 9.99, 10, "9.99" → 9.99, 10.0, 9.99
}

@ConvertToFloat

Akzeptiert Float, Int, Double oder String aus JSON → konvertiert zu Float?:

struct Response: Codable {
    @ConvertToFloat var rating: Float?  // 4.5, 5, "4.5" → 4.5, 5.0, 4.5
}

JSON-Schlüsselpfad-Decoder

Verschachtelte JSON-Werte extrahieren, ohne die gesamte Antwort zu parsen:

// JSON: { "code": 200, "data": { "list": [{ "id": 1 }, { "id": 2 }] } }

// Ein einzelnes verschachteltes Objekt extrahieren
let firstItem = try await Mesh()
    .setRequestMethod(.get)
    .setUrlPath("/items")
    .request(of: Item.self, modelKeyPath: "data.list.0")

// Der KeyPath-Decoder funktioniert auch direkt:
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")

Der Standard-Decoder konfiguriert automatisch:

  • keyDecodingStrategy = .convertFromSnakeCase (snake_case → camelCase)
  • dateDecodingStrategy = .iso8601

Combine + SwiftUI Integration

ObservableObject-Muster

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-Verwendung

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-Verwendung

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()
    }
}

Fehlerbehandlung

SwiftMesh normalisiert häufige Netzwerkfehler in benutzerfreundliche NSError:

FehlercodeBeschreibung
NSURLErrorNotConnectedToInternetKeine Internetverbindung
NSURLErrorTimedOutAnfrage-Timeout
NSURLErrorCannotFindHostHost nicht gefunden
NSURLErrorCannotConnectToHostKann keine Verbindung zum Host herstellen
NSURLErrorNetworkConnectionLostVerbindung während der Anfrage verloren

Alle normalisierten Fehler geben die Meldung zurück: "Unable to connect to the server"

Fangen Sie Fehler in Ihrem async-Code:

do {
    let result = try await Mesh()
        .setUrlPath("/api")
        .request(of: Response.self)
} catch let error as NSError {
    print("Error \(error.code): \(error.localizedDescription)")
}

RetryPolicy

Eingebauter Wiederholungsmechanismus mit linearem Backoff (1s, 2s, 3s...):

// Standard: 3 Wiederholungen
let policy = RetryPolicy()

// Benutzerdefiniert: 5 Wiederholungen
let policy = RetryPolicy(maxRetryCount: 5)

// Mit Anfrage verwenden
let result = try await Mesh()
    .setUrlPath("/unstable-api")
    .setInterceptor(policy)
    .request(of: Response.self)

Logging

Beim App-Start aktivieren:

// Mit Apples unified Logger (os.log)
Mesh.enableLog(.log)

// Mit Swift print()
Mesh.enableLog(.print)

Die Ausgabe umfasst:

  • cURL-Befehl (zur einfachen Reproduktion)
  • HTTP-Statuscode
  • Verstrichene Zeit
  • Hübsch formatierte JSON-Antwort

Referenz der Konfigurationsmethoden

Alle Methoden sind verkettbar (geben Self zurück):

Grundkonfiguration

MethodeTypBeschreibung
.setTimeout(_:)TimeIntervalAnfrage-Timeout in Sekunden (Standard: 15)
.setInterceptor(_:)RequestInterceptor?Wiederholungsrichtlinie / Berechtigungshandler
.setRequestMethod(_:)HTTPMethodGET, POST, PUT, DELETE, etc.
.setHeads(_:)[String: String]Anfragenspezifische Header
.setRequestEncoding(_:)ParameterEncodingURLEncoding, JSONEncoding, etc.
.setUrlHost(_:)String?Anfragenspezifischer URL-Host
.setUrlPath(_:)String?URL-Pfad
.setParameters(_:)[String: Any]?Anfrageparameter

Download-Konfiguration

MethodeTypBeschreibung
.setDownloadType(_:)DownloadType.download oder .resume

Einrichtung (SPM)

// Package.swift
dependencies: [
    .package(url: "https://github.com/zjinhu/SwiftMesh.git", from: "1.0.0")
]

Oder in Xcode: File > Add Packages... → https://github.com/zjinhu/SwiftMesh

Verwandte Skills