Introduction : des GET de base au client HTTP industriel
Une requête HTTP standard en Swift — ce sont trois lignes de code avec URLSession.shared.dataTask. Mais dans les projets réels (téléchargement de fichiers, authentification avec jetons, mise en cache, tentatives), cela ne suffit pas. Dans cet article, nous analyserons les techniques avancées de création d'un client HTTP en Swift pour iOS et macOS : protocoles personnalisés, gestion des sessions, multithreading via async/await et Combine, ainsi qu'une gestion fiable des erreurs. Ce contenu est destiné aux développeurs déjà familiers avec les bases de URLSession.
Architecture d'un client HTTP avancé
Un client industriel n'est pas simplement une fonction de requête, mais une couche d'abstraction. Divisons-le en trois niveaux :
- Transport Layer — travail de bas niveau avec URLSession, gestion de la configuration.
- Service Layer — sérialisation/désérialisation, traitement des codes de statut, tentatives.
- Application Layer — logique métier, mise en cache, authentification.
Commençons par la personnalisation de la Transport Layer via URLProtocol.
1. URLProtocol personnalisé pour intercepter et modifier les requêtes
URLProtocol est un mécanisme puissant pour intercepter toutes les requêtes réseau dans l'application. Il permet d'ajouter la journalisation, la signature des requêtes ou des données simulées pour les tests.
import Foundation
class LoggingProtocol: URLProtocol { override class func canInit(with request: URLRequest) -> Bool { // Intercepter toutes les requêtes HTTP/HTTPS return request.url?.scheme == "http" || request.url?.scheme == "https" }
override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request }
override func startLoading() { print("📤 Requête : \\(request.httpMethod ?? "GET") \\(request.url?.absoluteString ?? "")") print("📦 En-têtes : \\(request.allHTTPHeaderFields ?? [:])")
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in guard let self = self else { return } if let error = error { print("❌ Erreur : \\(error.localizedDescription)") self.client?.urlProtocol(self, didFailWithError: error) } else if let data = data, let response = response { print("✅ Réponse : \\(response.expectedContentLength) octets") self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed) self.client?.urlProtocol(self, didLoad: data) } self.client?.urlProtocolDidFinishLoading(self) } task.resume() }
override func stopLoading() { // Ne rien faire, la tâche se termine d'elle-même }}
// Enregistrement du protocoleURLProtocol.registerClass(LoggingProtocol.self)let config = URLSessionConfiguration.defaultconfig.protocolClasses = [LoggingProtocol.self]let session = URLSession(configuration: config)Désormais, toutes les requêtes via cette session seront journalisées. De la même manière, on peut ajouter l'ajout automatique d'un jeton Bearer ou le traitement du statut 401.
2. Gestion des sessions : configuration personnalisée et multithreading
Utilisez différentes URLSessionConfiguration pour différents objectifs : .default pour les requêtes normales, .ephemeral pour les données privées (sans cache ni cookies), .background pour le téléchargement en arrière-plan.
let backgroundConfig = URLSessionConfiguration.background(withIdentifier: "com.pythonlib.background")backgroundConfig.isDiscretionary = truebackgroundConfig.sessionSendsLaunchEvents = truelet backgroundSession = URLSession(configuration: backgroundConfig, delegate: self, delegateQueue: nil)Pour le multithreading, utilisez async/await (Swift 5.5+) :
func performRequest(url: URL) async throws -> Data { let (data, response)