Compétence IA SwiftMesh
Une référence IA complète pour utiliser SwiftMesh — un emballage autour d'Alamofire + Codable avec async/await, Combine, configuration fluide, téléchargement de fichiers, analyse de chemins clés JSON, wrappers Codable résilients et journalisation intégrée.
Fiche de Référence Rapide
| Fonctionnalité | Méthode | Description |
|---|---|---|
| Requête GET | .request(of: Model.self) | Décoder la réponse en modèle Codable |
| Chemin clé | .request(of: Model.self, modelKeyPath: "data.user") | Extraire du JSON imbriqué |
| Téléchargement de fichier | .upload(of: Result.self) | Télécharger un fichier/Data/flux/multipart |
| Téléchargement de fichier | .download() | Télécharger ou reprendre un téléchargement |
| Données brutes | .requestData() | Obtenir les données brutes de la réponse |
| Chaîne brute | .requestString() | Obtenir la réponse sous forme de chaîne |
| Politique de reprise | .setInterceptor(RetryPolicy()) | Reprise automatique avec backoff |
| Journalisation | Mesh.enableLog() | Activer la journalisation réseau |
Architecture de Base
SwiftMesh utilise un modèle de construction sur la classe Mesh. Chaque méthode de configuration retourne Self, permettant un enchaînement fluide. Le flux est :
Configurer (Mesh + Config) → Exécuter (Requête/Téléchargement/Téléchargement de fichier) → Gérer (Handle)
Structure des Fichiers
| Fichier | Objectif |
|---|---|
Mesh.swift | Classe de construction de base avec toutes les propriétés |
Config.swift | Setters fluides enchaînables + configuration globale |
Request.swift | Exécution de requête async/await |
Handle.swift | Construction d'URL, gestion d'erreurs, traitement de réponse, RetryPolicy |
Upload.swift | Téléchargement de fichier (fichier, données, flux, multipart) |
Download.swift | Téléchargement de fichier (standard, reprenable) |
KeyPath.swift | Décodeur de chemin clé JSON pour extraction imbriquée |
Codable+.swift | Wrappers de propriété résilients (@Default, @IgnoreError, @ConvertTo*) |
Log.swift | Journaliseur réseau (cURL, statut, timing, JSON) |
Modèles d'Utilisation
1. Requête GET de Base
let result = try await Mesh()
.setRequestMethod(.get)
.setUrlHost("https://api.example.com")
.setUrlPath("/weather/city/101030100")
.request(of: Weather.self)
2. GET avec Extraction de Chemin Clé JSON
Extraire uniquement une partie imbriquée de la réponse JSON sans analyser toute la structure :
// 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. Requête POST avec Paramètres
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 avec Encodage JSON
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. Requête avec En-têtes Personnalisés
let result = try await Mesh()
.setUrlHost("https://api.example.com")
.setUrlPath("/secure/data")
.setHeads(["Authorization": "Bearer token123"])
.request(of: SecureData.self)
6. Requête avec Politique de Reprise
let result = try await Mesh()
.setUrlHost("https://api.example.com")
.setUrlPath("/unstable-api")
.setInterceptor(RetryPolicy(maxRetryCount: 3))
.request(of: Data.self)
7. Requête Basée sur URLRequest
Lorsque vous avez déjà un 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. Réponse de Données Brutes
let data = try await Mesh()
.setRequestMethod(.get)
.setUrlHost("https://api.example.com")
.setUrlPath("/raw")
.requestData()
9. Réponse de Chaîne Brute
let string = try await Mesh()
.setRequestMethod(.get)
.setUrlHost("https://api.example.com")
.setUrlPath("/text")
.requestString()
10. Téléchargement de Fichier
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. Téléchargement Reprenable
let fileURL = try await Mesh()
.setUrlHost("https://example.com")
.setUrlPath("/files/large-file.zip")
.setDownloadType(.resume)
.setResumeData(savedResumeData) // à partir d'un téléchargement interrompu précédent
.download()
12. Téléchargement de Fichier Unique (URL)
let result = try await Mesh()
.setRequestMethod(.post)
.setUrlHost("https://api.example.com")
.setUrlPath("/upload")
.setUploadType(.file)
.setFileURL(fileURL)
.upload(of: UploadResult.self)
13. Téléchargement de Fichier Unique (Données)
let result = try await Mesh()
.setRequestMethod(.post)
.setUrlHost("https://api.example.com")
.setUrlPath("/upload")
.setUploadType(.data)
.setFileData(imageData)
.upload(of: UploadResult.self)
14. Téléchargement de Formulaire Multipart
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 avec UploadDatas Pré-construites
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"]) // champs de formulaire supplémentaires
.upload(of: BatchResult.self)
16. Délai d'Attente Personnalisé
let result = try await Mesh()
.setUrlHost("https://api.example.com")
.setUrlPath("/slow-api")
.setTimeout(60) // 60 secondes
.request(of: Response.self)
Configuration Globale
Définissez ces valeurs une fois au lancement de l'application (par exemple, dans AppDelegate) :
// Activer la journalisation réseau
Mesh.enableLog(.log) // ou .print
// Définir les en-têtes globaux par défaut
Mesh.setHeaders(["Authorization": "Bearer token", "App-Version": "1.0"])
// Définir les paramètres globaux par défaut
Mesh.setParameters(["platform": "ios", "sdk_version": "2.0"])
// Définir l'hôte URL global
Mesh.setUrlHost("https://api.example.com")
Ensuite, la configuration par requête ne nécessite que le chemin :
// Utilise les en-têtes, paramètres et urlHost globaux
let result = try await Mesh()
.setUrlPath("/weather")
.request(of: Weather.self)
Wrappers de Propriété Codable Résilients
Gérer les réponses API incohérentes avec élégance sans échecs de décodage.
Wrappers @Default
Fournir des valeurs de repli lorsque des champs sont manquants ou invalides :
struct Response: Codable {
@Default.True var isEnabled: Bool // Manquant → true
@Default.False var isDeleted: Bool // Manquant → false
@Default.EmptyString var name: String // Manquant → ""
@Default.EmptyInt var count: Int // Manquant → 0
@Default.EmptyArray var tags: [String] // Manquant → []
@Default.EmptyDictionary var meta: [String: Int] // Manquant → [:]
@Default.Now var createdAt: Date // Manquant → Date()
}
@IgnoreError
Retourne nil au lieu de lever une exception lorsqu'un champ ne peut pas être décodé :
struct Response: Codable {
@IgnoreError var description: String? // Type invalide → nil (pas de plantage)
@IgnoreError var nested: NestedModel? // Manquant/malformé → nil
}
@ConvertToString
Accepte les chaînes, Int ou Double du JSON → convertit en String? :
struct Response: Codable {
@ConvertToString var version: String? // "1.0", 1, ou 1.0 → "1.0", "1", "1.0"
}
@ConvertToInt
Accepte Int, String ou Double du JSON → convertit en Int? :
struct Response: Codable {
@ConvertToInt var count: Int? // 42, "42", ou 42.9 → 42, 42, 42
}
@ConvertToDouble
Accepte Double, Int, Float ou String du JSON → convertit en Double? :
struct Response: Codable {
@ConvertToDouble var price: Double? // 9.99, 10, "9.99" → 9.99, 10.0, 9.99
}
@ConvertToFloat
Accepte Float, Int, Double ou String du JSON → convertit en Float? :
struct Response: Codable {
@ConvertToFloat var rating: Float? // 4.5, 5, "4.5" → 4.5, 5.0, 4.5
}
Décodeur de Chemin Clé JSON
Extraire des valeurs JSON imbriquées sans analyser toute la réponse :
// JSON: { "code": 200, "data": { "list": [{ "id": 1 }, { "id": 2 }] } }
// Extraire un seul objet imbriqué
let firstItem = try await Mesh()
.setRequestMethod(.get)
.setUrlPath("/items")
.request(of: Item.self, modelKeyPath: "data.list.0")
// Le décodeur KeyPath fonctionne également directement :
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")
Le décodeur par défaut se configure automatiquement :
keyDecodingStrategy = .convertFromSnakeCase(snake_case → camelCase)dateDecodingStrategy = .iso8601
Intégration Combine + SwiftUI
Modèle 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 }
}
}
}
}
Utilisation avec 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() }
}
}
Utilisation avec 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()
}
}
Gestion des Erreurs
SwiftMesh normalise les erreurs réseau courantes en NSError convivial :
| Code d'Erreur | Description |
|---|---|
NSURLErrorNotConnectedToInternet | Pas de connexion Internet |
NSURLErrorTimedOut | Délai d'attente de la requête dépassé |
NSURLErrorCannotFindHost | Hôte introuvable |
NSURLErrorCannotConnectToHost | Impossible de se connecter à l'hôte |
NSURLErrorNetworkConnectionLost | Connexion perdue pendant la requête |
Toutes les erreurs normalisées retournent le message : "Impossible de se connecter au serveur"
Attraper les erreurs dans votre code asynchrone :
do {
let result = try await Mesh()
.setUrlPath("/api")
.request(of: Response.self)
} catch let error as NSError {
print("Erreur \(error.code) : \(error.localizedDescription)")
}
RetryPolicy (Politique de Reprise)
Reprise intégrée avec backoff linéaire (1s, 2s, 3s...) :
// Par défaut : 3 reprises
let policy = RetryPolicy()
// Personnalisé : 5 reprises
let policy = RetryPolicy(maxRetryCount: 5)
// Utilisation avec une requête
let result = try await Mesh()
.setUrlPath("/unstable-api")
.setInterceptor(policy)
.request(of: Response.self)
Journalisation
Activer au lancement de l'application :
// Utilisation du journaliseur unifié d'Apple (os.log)
Mesh.enableLog(.log)
// Utilisation de print() Swift
Mesh.enableLog(.print)
La sortie inclut :
- Commande cURL (pour reproduction facile)
- Code d'état HTTP
- Temps écoulé
- Réponse JSON joliment formatée
Référence des Méthodes de Configuration
Toutes les méthodes sont enchaînables (retournent Self) :
Configuration de Base
| Méthode | Type | Description |
|---|---|---|
.setTimeout(_:) | TimeInterval | Délai d'attente de la requête en secondes (par défaut : 15) |
.setInterceptor(_:) | RequestInterceptor? | Politique de reprise / gestionnaire d'identifiants |
.setRequestMethod(_:) | HTTPMethod | GET, POST, PUT, DELETE, etc. |
.setHeads(_:) | [String: String] | En-têtes par requête |
.setRequestEncoding(_:) | ParameterEncoding | URLEncoding, JSONEncoding, etc. |
.setUrlHost(_:) | String? | Hôte URL par requête |
.setUrlPath(_:) | String? | Chemin URL |
.setParameters(_:) | [String: Any]? | Paramètres de la requête |
Configuration du Téléchargement
| Méthode | Type | Description |
|---|---|---|
.setDownloadType(_:) | DownloadType | .download ou .resume |
.setResumeData(_:) | Data? | Données de reprise pour téléchargements reprenables |
.setDestination(_:) | DownloadRequest.Destination? | Chemin de destination personnalisé |
Configuration du Téléchargement de Fichier
| Méthode | Type | Description |
|---|---|---|
.setUploadType(_:) | UploadType | .file, .data, .stream, .multipart |
.setFileURL(_:) | URL? | URL du fichier à télécharger |
.setFileData(_:) | Data? | Données à télécharger |
.setUploadDatas(_:) | [MultipleUpload]? | Tableau de données de téléchargement multipart |
.setAddformData(name:fileName:fileData:mimeType:) | - | Ajouter un champ de formulaire multipart |
Dépendances
SwiftMesh nécessite :
Alamofire ~> 5.0
Inclure dans votre Package.swift :
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.0.0"),
.package(url: "https://github.com/zjinhu/SwiftMesh.git", branch: "main")
]
Considérations de Performance
- Utilisez
modelKeyPathpour les structures JSON volumineuses afin d'éviter une surcharge de décodage inutile - Les wrappers @Default et @IgnoreError ont un coût de performance minime
- La journalisation peut être désactivée en production avec
Mesh.enableLog(.none) - Politique de reprise avec backoff linéaire pour éviter les surcharges du serveur
- Les téléchargements reprenables reprennent à partir de l'octet interrompu, économisant la bande passante
Dépannage
| Problème | Solution |
|---|---|
| Échec du décodage | Vérifiez que le modèle est conforme à Codable ; utilisez modelKeyPath ou des wrappers résilients |
| Timeout des requêtes | Augmentez le délai d'attente ou vérifiez l'état du réseau |
| Échec du téléchargement | Vérifiez le type MIME et la taille du fichier ; activez la journalisation pour diagnostiquer |
| Erreur de compilation (Alamofire) | Assurez-vous que la version d'Alamofire est ~> 5.0 |
| Journalisation non activée | Appelez Mesh.enableLog(.log) ou .print avant d'effectuer des requêtes |
| Échec du téléchargement reprenable | Assurez-vous que le serveur prend en charge les téléchargements partiels (en-tête Range) |
Pour plus de détails, consultez le dépôt GitHub SwiftMesh et la documentation Alamofire.