Abhängigkeitsinjektion mit DITranquillity

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.


Komponentenstruktur

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:


Dateistruktur

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) { // Implementation } } 

 protocol Networking: class { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) } class MyNetworking: Networking { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) { // Implementation } } 

 protocol Router: class { func presentNewController() } class MyRouter: Router { unowned let viewController: ViewController init(viewController: ViewController) { self.viewController = viewController } func presentNewController() { // Implementation } } 

 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) { // registrations will be placed here } } 

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() // 1 container.append(framework: ApplicationDependency.self) // 2 assert(container.validate(checkGraphCycles: true)) // 3 return container }() 

  1. Initialisieren Sie den Container
  2. Fügen Sie eine Beschreibung des Diagramms hinzu.
  3. 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

Source: https://habr.com/ru/post/de450788/


All Articles