Einleitung: Warum ein ordentlicher HTTP-Client für Ihre Swift-App wichtig ist
Moderne iOS- und macOS-Apps funktionieren selten ohne Interaktion mit entfernten Servern. Ob es darum geht, Daten von einer REST-API zu laden, Analysen zu senden oder mit Cloud-Diensten zu arbeiten — ein HTTP-Client in Swift ist die Grundlage der Netzwerkkommunikation. Viele Entwickler, insbesondere Anfänger, machen jedoch typische Fehler: Sie ignorieren die Fehlerbehandlung, nutzen keine modernen Sprachfunktionen (async/await) oder erzeugen aus Netzwerkaufrufen „Spaghetticode". In diesem Artikel zeigen wir, wie Sie unter Einhaltung der Best Practices einen zuverlässigen und leistungsstarken HTTP-Client in Swift schreiben.
1. URLSession-Grundlagen: Das Herzstück von Netzwerkanfragen
In der Swift-Standardbibliothek übernehmen das Foundation-Framework und die URLSession-Klasse die Netzwerkkommunikation. Dies ist ein leistungsstarkes Werkzeug, das Sitzungskonfiguration, Caching, Hintergrundanfragen und vieles mehr unterstützt.
1.1. Erstellen eines einfachen HTTP-Clients
Beginnen wir mit einer einfachen Struktur, die GET-Anfragen durchführt. Wir verwenden einen Enum zur Beschreibung möglicher Fehler und ein Protokoll für die Flexibilität:
import Foundation
enum HTTPClientError: Error { case invalidURL case noData case decodingError(Error) case networkError(Error) case statusCode(Int)}
protocol HTTPClientProtocol { func get<T: Decodable>(from urlString: String, responseType: T.Type) async throws -> T}
final class HTTPClient: HTTPClientProtocol { private let session: URLSession private let decoder: JSONDecoder init(session: URLSession = .shared, decoder: JSONDecoder = JSONDecoder()) { self.session = session self.decoder = decoder } func get<T: Decodable>(from urlString: String, responseType: T.Type) async throws -> T { guard let url = URL(string: urlString) else { throw HTTPClientError.invalidURL } var request = URLRequest(url: url) request.httpMethod = "GET" request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await session.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw HTTPClientError.networkError(NSError(domain: "Invalid response", code: -1)) } guard (200...299).contains(httpResponse.statusCode) else { throw HTTPClientError.statusCode(httpResponse.statusCode) } do { return try decoder.decode(T.self, from: data) } catch { throw HTTPClientError.decodingError(error) } }}1.2. Warum async/await?
Vor Swift 5.5 verwendeten Entwickler Closures (Completion-Handler) oder Bibliotheken von Drittanbietern (Alamofire). Async/await macht den Code linear, klar und leicht lesbar. Fehler werden mit do-catch behandelt, anstatt mit verschachtelten Closures.
2. POST, PUT, DELETE: Arbeiten mit verschiedenen HTTP-Methoden
Ein echter HTTP-Client sollte alle wichtigen Methoden unterstützen. Fügen wir dem Protokoll Methoden zum Senden von Daten und Aktualisieren von Ressourcen hinzu.
2.1. Erweiterung des Protokolls für POST und PUT
extension HTTPClientProtocol { func post<T: Decodable, U: Encodable>( to urlString: String, body: U, responseType: T.Type ) async throws -> T { guard let url = URL(string: urlString) else { throw HTTPClientError.invalidURL } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("application/json", forHTTPHeaderField: "Accept") request.httpBody = try JSONEncoder().encode(body) return try a