Einleitung: Warum Ihr Code Dependency Injection benötigt?
Jeder iOS-Entwickler kennt die Situation, in der eine Klasse "fett" und fest an konkrete Implementierungen gebunden wird. Sie ändern eine Klasse, aber Dutzende andere müssen umgeschrieben werden. Kommt Ihnen das bekannt vor? Dies ist das klassische Problem der engen Kopplung (tight coupling).
Dependency Injection (DI) ist ein Entwurfsmuster, das dieses Problem löst. Anstatt dass ein Objekt seine Abhängigkeiten selbst erstellt, werden diese von außen "injiziert". Das macht den Code flexibel, testbar und konform zu den SOLID-Prinzipien (insbesondere dem Prinzip der Abhängigkeitsumkehr).
In diesem Artikel werden wir untersuchen, was DI in Swift ist, welche Implementierungsmöglichkeiten es gibt und wie es das Leben eines iOS/macOS-Entwicklers vereinfacht. Wir verwenden einfache Beispiele, die auch für Anfänger verständlich sind.
Was ist Dependency Injection? Ein einfaches Beispiel
Stellen Sie sich vor, Sie backen eine Pizza. Die Variante ohne DI: Sie bauen selbst Weizen an, mahlen Mehl, melken eine Kuh für den Käse. Das ist enge Kopplung. Die Variante mit DI: Sie bestellen einfach die Zutaten (Abhängigkeiten) bei einem Lieferanten. Sie wissen nicht, wie das Mehl hergestellt wurde – nur das Ergebnis ist wichtig.
In der Programmierung ist es dasselbe. Angenommen, wir haben eine Klasse OrderService, die Bestellungen per E-Mail versendet:
// ❌ Ohne DI — enge Kopplungclass OrderService { let mailer = Mailer() // Erstellt die Abhängigkeit selbst
func process(order: Order) { mailer.send(email: "Neue Bestellung: \\(order.id)") }}Problem: Wir können Mailer nicht durch eine Testversion ersetzen. Die Klasse OrderService "weiß" zu viel.
// ✅ Mit DI — Abhängigkeit wird injiziertclass OrderService { let mailer: MailerProtocol // Abhängig von der Abstraktion, nicht vom Konkreten
init(mailer: MailerProtocol) { // Injektion über den Initialisierer self.mailer = mailer }
func process(order: Order) { mailer.send(email: "Neue Bestellung: \\(order.id)") }}Jetzt können wir jedes Objekt übergeben, das dem Protokoll MailerProtocol entspricht – einen echten Mailer oder einen "Stub" für Tests.
Drei Hauptmethoden der Abhängigkeitsinjektion in Swift
Es gibt drei gängige Methoden, um eine Abhängigkeit an eine Klasse zu übergeben. Betrachten wir jede.
1. Constructor Injection (über den Initialisierer)
Die gebräuchlichste und bevorzugte Methode. Die Abhängigkeit wird bei der Erstellung des Objekts übergeben. Dies stellt sicher, dass sich das Objekt immer in einem gültigen Zustand befindet.
protocol NetworkServiceProtocol { func fetchData() -> String}
class RealNetworkService: NetworkServiceProtocol { func fetchData() -> String { return "Echte Daten vom Server" }}
class MockNetworkService: NetworkServiceProtocol { func fetchData() -> String { return "Testdaten" }}
class DataManager { private let network: NetworkServiceProtocol
// Abhängigkeit wird über init übergeben init(network: NetworkServiceProtocol) { self.network = network }
func load() -> String { return network.fetchData() }}
// Verwendunglet realManager = DataManager(network: RealNetworkService())let mockManager = DataManager(network: MockNetworkService())Vorteile: Es ist klar ersichtlich, welche Abhängigkeiten benötigt werden. Das Objekt ist sofort einsatzbereit. Nachteile: Bei vielen Abhängigkeiten wird der Initialisierer umfangreich.
2. Property Injection (über eine Eigenschaft)
Die Abhängigkeit wird nach der Erstellung des Objekts gesetzt. Wird häufig verwendet, wenn die Abhängigkeit optional ist oder zum Zeitpunkt der Initialisierung schwer zu erhalten ist.
class UserViewModel { var apiService: NetworkServiceProtocol? // Optionale Abhängigkeit
func loadUser() { apiServi