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
| Funktion | Methode | Beschreibung |
|---|---|---|
| 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 |
| Logging | Mesh.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
| Datei | Zweck |
|---|---|
Mesh.swift | Kern-Builder-Klasse mit allen Eigenschaften |
Config.swift | Fließende verkettbare Setter + globale Konfiguration |
Request.swift | async/await-Anfrageausführung |
Handle.swift | URL-Konstruktion, Fehlerbehandlung, Antwortverarbeitung, RetryPolicy |
Upload.swift | Datei-Upload (Datei, Daten, Stream, Multipart) |
Download.swift | Datei-Download (Standard, fortsetzbar) |
KeyPath.swift | JSON-Schlüsselpfad-Decoder für verschachtelte Extraktion |
Codable+.swift | Robuste Property-Wrapper (@Default, @IgnoreError, @ConvertTo*) |
Log.swift | Netzwerk-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:
| Fehlercode | Beschreibung |
|---|---|
NSURLErrorNotConnectedToInternet | Keine Internetverbindung |
NSURLErrorTimedOut | Anfrage-Timeout |
NSURLErrorCannotFindHost | Host nicht gefunden |
NSURLErrorCannotConnectToHost | Kann keine Verbindung zum Host herstellen |
NSURLErrorNetworkConnectionLost | Verbindung 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
| Methode | Typ | Beschreibung |
|---|---|---|
.setTimeout(_:) | TimeInterval | Anfrage-Timeout in Sekunden (Standard: 15) |
.setInterceptor(_:) | RequestInterceptor? | Wiederholungsrichtlinie / Berechtigungshandler |
.setRequestMethod(_:) | HTTPMethod | GET, POST, PUT, DELETE, etc. |
.setHeads(_:) | [String: String] | Anfragenspezifische Header |
.setRequestEncoding(_:) | ParameterEncoding | URLEncoding, JSONEncoding, etc. |
.setUrlHost(_:) | String? | Anfragenspezifischer URL-Host |
.setUrlPath(_:) | String? | URL-Pfad |
.setParameters(_:) | [String: Any]? | Anfrageparameter |
Download-Konfiguration
| Methode | Typ | Beschreibung |
|---|---|---|
.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