Einleitung: Von einfachen GET-Anfragen zum professionellen HTTP-Client
Eine Standard-HTTP-Anfrage in Swift besteht aus drei Codezeilen mit URLSession.shared.dataTask. In realen Projekten (Datei-Uploads, Token-basierte Authentifizierung, Caching, Wiederholungsversuche) reicht dies jedoch nicht aus. In diesem Artikel behandeln wir fortgeschrittene Techniken zur Erstellung eines HTTP-Clients in Swift für iOS und macOS: benutzerdefinierte Protokolle, Sitzungsverwaltung, Multithreading mit async/await und Combine sowie eine zuverlässige Fehlerbehandlung. Das Material richtet sich an Entwickler, die bereits mit den Grundlagen von URLSession vertraut sind.
Architektur eines fortgeschrittenen HTTP-Clients
Ein professioneller Client ist nicht nur eine einfache Anfragefunktion, sondern eine Abstraktionsebene. Wir unterteilen ihn in drei Ebenen:
- Transport Layer – Low-Level-Arbeit mit URLSession, Konfigurationsverwaltung.
- Service Layer – Serialisierung/Deserialisierung, Behandlung von Statuscodes, Wiederholungsversuche.
- Application Layer – Geschäftslogik, Caching, Authentifizierung.
Beginnen wir mit der Anpassung des Transport Layer über URLProtocol.
1. Benutzerdefiniertes URLProtocol zum Abfangen und Modifizieren von Anfragen
URLProtocol ist ein leistungsstarker Mechanismus zum Abfangen aller Netzwerkanfragen in einer App. Damit können Sie Logging, Signieren von Anfragen oder Mock-Daten für Tests hinzufügen.
import Foundation
class LoggingProtocol: URLProtocol { override class func canInit(with request: URLRequest) -> Bool { // Alle HTTP/HTTPS-Anfragen abfangen return request.url?.scheme == "http" || request.url?.scheme == "https" }
override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request }
override func startLoading() { print("📤 Anfrage: \\(request.httpMethod ?? "GET") \\(request.url?.absoluteString ?? "")") print("📦 Header: \\(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("❌ Fehler: \\(error.localizedDescription)") self.client?.urlProtocol(self, didFailWithError: error) } else if let data = data, let response = response { print("✅ Antwort: \\(response.expectedContentLength) Bytes") self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed) self.client?.urlProtocol(self, didLoad: data) } self.client?.urlProtocolDidFinishLoading(self) } task.resume() }
override func stopLoading() { // Nichts tun, die Aufgabe wird von selbst beendet }}
// Protokoll registrierenURLProtocol.registerClass(LoggingProtocol.self)let config = URLSessionConfiguration.defaultconfig.protocolClasses = [LoggingProtocol.self]let session = URLSession(configuration: config)Jetzt werden alle Anfragen über diese Sitzung protokolliert. Analog können Sie das automatische Hinzufügen eines Bearer-Tokens oder die Behandlung des 401-Statuscodes hinzufügen.
2. Sitzungsverwaltung: Benutzerdefinierte Konfiguration und Multithreading
Verwenden Sie verschiedene URLSessionConfiguration für unterschiedliche Zwecke: .default für normale Anfragen, .ephemeral für private Daten (ohne Cache und Cookies), .background für Hintergrund-Downloads.
let backgroundConfig = URLSessionConfiguration.background(withIdentifier: "com.pythonlib.background")backgroundConfig.isDiscretionary = truebackgroundConfig.sessionSendsLaunchEvents = truelet backgroundSession = URLSession(configuration: backgroundConfig, delegate: self, delegateQueue: nil)Für Multithreading verwenden Sie async/await (Swift 5.5+):
func performRequest(url: URL) async throws -> Data { let (data, response)