Die Abhängigkeitsinjektion ist ein recht beliebtes Muster, mit dem Sie das System flexibel konfigurieren und die Abhängigkeiten der Komponenten dieses Systems korrekt voneinander aufbauen können. Dank der Eingabe können Sie in Swift praktische Frameworks verwenden, mit denen Sie das Abhängigkeitsdiagramm sehr kurz beschreiben können. Heute möchte ich ein wenig über eines dieser Frameworks DITranquillity
- DITranquillity
.
Dieses Tutorial behandelt die folgenden Bibliotheksfunktionen:
- Typregistrierung
- Initialisierungsbereitstellung
- Einbetten in eine Variable
- Zyklische Abhängigkeiten von Komponenten
- Verwenden der Bibliothek mit
UIStoryboard
Komponentenbeschreibung
Die Anwendung besteht aus den folgenden Hauptkomponenten: ViewController
, Router
, Presenter
, Networking
- dies sind ziemlich häufige Komponenten in jeder iOS-Anwendung.
ViewController
und Router
werden zyklisch ineinander eingeführt.
Vorbereitung
Erstellen Sie zunächst eine Single View-Anwendung in Xcode und fügen Sie mit CocoaPods DITranquillity hinzu . Erstellen Sie die erforderliche Hierarchie von Dateien, fügen Sie dann Main.storyboard einen zweiten Controller hinzu und verbinden Sie ihn mit StoryboardSegue
. Als Ergebnis sollte die folgende Dateistruktur erhalten werden:
Erstellen Sie Abhängigkeiten in den Klassen wie folgt:
Komponentendeklaration protocol Presenter: class { func getCounter(completion: @escaping (Int) -> Void) } class MyPresenter: Presenter { private let networking: Networking init(networking: Networking) { self.networking = networking } func getCounter(completion: @escaping (Int) -> Void) {
protocol Networking: class { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) } class MyNetworking: Networking { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) {
protocol Router: class { func presentNewController() } class MyRouter: Router { unowned let viewController: ViewController init(viewController: ViewController) { self.viewController = viewController } func presentNewController() {
class ViewController: UIViewController { var presenter: Presenter! var router: Router! }
Einschränkungen
Im Gegensatz zu anderen Klassen wird der ViewController
nicht von uns erstellt, sondern von der UIKit-Bibliothek in der UIStoryboard.instantiateViewController
Implementierung. Daher können wir mithilfe des Storyboards keine Abhängigkeiten mit dem Initialisierer in die Erben des UIViewController
. So ist es auch mit den Erben von UIView
und UITableViewCell
.
Beachten Sie, dass Objekte, die hinter Protokollen versteckt sind, in alle Klassen eingebettet sind. Dies ist eine der Hauptaufgaben beim Implementieren von Abhängigkeiten - Abhängigkeiten nicht von Implementierungen, sondern von Schnittstellen herzustellen. Dies wird in Zukunft helfen, verschiedene Protokollimplementierungen zum Wiederverwenden oder Testen von Komponenten bereitzustellen.
Abhängigkeitsinjektion
Nachdem alle Komponenten des Systems erstellt wurden, fahren wir mit der Verbindung von Objekten untereinander fort. In DITranquillity ist der Ausgangspunkt DIContainer
, der die Registrierung mit der Methode container.register(...)
hinzufügt. Um die Abhängigkeiten in Teile zu trennen, werden DIFramework
und DIPart
, die implementiert werden müssen. Der DIFramework
erstellen wir nur eine ApplicationDependency
Klasse, die DIFramework
implementiert und als Registrierungsort für alle Abhängigkeiten dient. Für die DIFramework
Schnittstelle müssen Sie nur eine Methode implementieren - load(container:)
.
class ApplicationDependency: DIFramework { static func load(container: DIContainer) {
Beginnen wir mit der einfachsten Anmeldung ohne Abhängigkeiten - MyNetworking
container.register(MyNetworking.init)
Diese Registrierung verwendet die Implementierung über den Initialisierer. Trotz der Tatsache, dass die Komponente selbst keine Abhängigkeiten aufweist, muss der Initialisierer bereitgestellt werden, um der Bibliothek klar zu machen, wie die Komponente erstellt wird.
Registrieren MyPresenter
ähnlicher Weise MyPresenter
und MyRouter
.
container.register1(MyPresenter.init) container.register1(MyRouter.init)
Hinweis: Beachten Sie, dass nicht das register
verwendet wird, sondern das register1
. Leider ist dies erforderlich, um anzugeben, ob das Objekt eine und nur eine Abhängigkeit im Initialisierer hat. Das heißt, wenn es 0 oder zwei oder mehr Abhängigkeiten gibt, müssen Sie nur register
. Diese Einschränkung ist ein Fehler von Swift Version 4.0 und höher.
Es ist Zeit, unseren ViewController
zu registrieren. Es injiziert Objekte nicht über den Initialisierer, sondern direkt in die Variable, sodass die Registrierungsbeschreibung etwas mehr ausfällt.
container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter)
Die Syntax des Formulars \.presenter
lautet SwiftKeyPath, dank dessen Abhängigkeiten \.presenter
implementiert werden können. Da Router
und ViewController
zyklisch voneinander abhängig sind, müssen Sie dies explizit mit cycle: true
angeben. Die Bibliothek selbst kann diese Abhängigkeiten ohne explizite Angabe auflösen. Diese Anforderung wurde jedoch eingeführt, damit die Person, die das Diagramm liest, sofort versteht, dass es Zyklen in der Abhängigkeitskette gibt. Beachten Sie auch, dass NICHT ViewController.init
, sondern ViewController.self
. Dies wurde oben im Abschnitt Einschränkungen beschrieben .
Es ist auch erforderlich, das UIStoryboard
mit einer speziellen Methode zu registrieren.
container.registerStoryboard(name: "Main")
Jetzt haben wir das gesamte Abhängigkeitsdiagramm für einen Bildschirm beschrieben. Es gibt jedoch noch keinen Zugriff auf dieses Diagramm. Sie müssen einen DIContainer
erstellen, mit dem Sie auf die darin enthaltenen Objekte zugreifen können.
static let container: DIContainer = { let container = DIContainer()
- Initialisieren Sie den Container
- Fügen Sie eine Beschreibung des Diagramms hinzu.
- Wir überprüfen, ob wir alles richtig gemacht haben. Wenn ein Fehler gemacht wird, stürzt die Anwendung nicht beim Auflösen von Abhängigkeiten ab, sondern sofort beim Erstellen eines Diagramms
Dann müssen Sie den Container zum Ausgangspunkt der Anwendung machen. Zu diesem didFinishLaunchingWithOptions
implementieren AppDelegate
die didFinishLaunchingWithOptions
Methode in didFinishLaunchingWithOptions
anstatt Main.storyboard
als Startpunkt in den Projekteinstellungen anzugeben.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) let storyboard: UIStoryboard = ApplicationDependency.container.resolve() window?.rootViewController = storyboard.instantiateInitialViewController() window?.makeKeyAndVisible() return true }
Starten
Beim ersten Start tritt ein Drop auf und die Validierung schlägt aus folgenden Gründen fehl:
- Der Container findet die Typen
Router
, Presenter
, Networking
, da nur Objekte registriert wurden. Wenn wir nicht auf Implementierungen, sondern auf Schnittstellen zugreifen möchten, müssen wir die Schnittstellen explizit angeben - Der Container versteht es nicht, die zyklische Abhängigkeit aufzulösen, da explizit angegeben werden muss, welche Objekte nicht jedes Mal neu erstellt werden sollen, wenn das Diagramm aufgelöst wird.
Es ist einfach, den ersten Fehler zu beheben. Es gibt eine spezielle Methode, mit der Sie angeben können, unter welchen Protokollen die Methode im Container verfügbar ist.
container.register(MyNetworking.init) .as(check: Networking.self) {$0}
Wenn wir die Registrierung wie folgt beschreiben, sagen wir: Auf das MyNetworking
Objekt kann über das Networking
zugegriffen werden. Dies muss für alle Objekte erfolgen, die unter den Protokollen versteckt sind. {$0}
fügen {$0}
für die korrekte Überprüfung der Typen durch den Compiler hinzu.
Der zweite Fehler ist etwas komplizierter. Es ist notwendig, den sogenannten scope
, der beschreibt, wie oft das Objekt erstellt wird und wie viel es lebt. Für jede Registrierung, die an einer zirkulären Abhängigkeit teilnimmt, müssen Sie einen scope
angeben, der objectGraph
. Dadurch wird dem Container klar, dass beim Auflösen dieselben erstellten Objekte wiederverwendet werden müssen, anstatt jedes Mal erstellt zu werden. So stellt sich heraus:
container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter) .lifetime(.objectGraph) container.register1(MyRouter.init) .as(check: Router.self) {$0} .lifetime(.objectGraph)
Nach dem Neustart besteht der Container die Validierung erfolgreich und unser ViewController wird mit den erstellten Abhängigkeiten geöffnet. Sie können einen Haltepunkt in viewDidLoad
und sicherstellen.
Übergang zwischen Bildschirmen
Erstellen Sie als Nächstes zwei kleine Klassen, SecondViewController
und SecondPresenter
, fügen Sie SecondViewController
zum Storyboard hinzu und erstellen Sie einen Segue
zwischen ihnen mit der Kennung "RouteToSecond"
, mit dem Sie den zweiten Controller vom ersten öffnen können.
Fügen Sie unserer ApplicationDependency
für jede der neuen Klassen zwei weitere Registrierungen hinzu:
container.register(SecondViewController.self) .injection(\.secondPresenter) container.register(SecondPresenter.init)
.as
nicht erforderlich, .as
anzugeben, da wir SecondPresenter
hinter dem Protokoll versteckt SecondPresenter
, sondern die Implementierung direkt verwenden. Dann rufen wir in der viewDidAppear
Methode des ersten Controllers performSegue(withIdentifier: "RouteToSecond", sender: self)
, start, der zweite Controller wird geöffnet, in dem die secondPresenter
Abhängigkeit angebracht werden soll. Wie Sie sehen können, wurde im Container ein zweiter Controller aus UIStoryboard
und die Abhängigkeiten erfolgreich UIStoryboard
.
Fazit
Mit dieser Bibliothek können Sie bequem mit zyklischen Abhängigkeiten und Storyboards arbeiten und in Swift die automatische Typinferenz verwenden, die eine sehr kurze und flexible Syntax für die Beschreibung eines Abhängigkeitsdiagramms bietet.
Referenzen
Vollständiger Beispielcode in der Github-Bibliothek
DITranquillity auf Github
Englischer Artikel