Injection de dépendance avec DITranquillity

L'injection de dépendances est un modèle assez populaire qui vous permet de configurer le système de manière flexible et de créer correctement les dépendances des composants de ce système les uns sur les autres. Grâce à la saisie, Swift vous permet d'utiliser des frameworks pratiques avec lesquels vous pouvez décrire très brièvement le graphe des dépendances. Aujourd'hui, je veux parler un peu de l'un de ces cadres - DITranquillity .


Ce didacticiel couvrira les fonctionnalités de bibliothèque suivantes:


  • Enregistrement de type
  • DĂ©ploiement d'initialisation
  • Incorporation dans une variable
  • DĂ©pendances cycliques des composants
  • Utilisation de la bibliothèque avec UIStoryboard

Description des composants


L'application se composera des principaux composants suivants: ViewController , Router , Presenter , Networking - ce sont des composants assez communs dans toute application iOS.


Structure des composants

ViewController et Router seront introduits l'un dans l'autre de manière cyclique.


La préparation


Tout d'abord, créez une application à vue unique dans Xcode, ajoutez DITranquillity à l'aide de CocoaPods . Créez la hiérarchie de fichiers nécessaire, puis ajoutez un deuxième contrôleur à Main.storyboard et connectez-le à l'aide de StoryboardSegue . Par conséquent, la structure de fichiers suivante doit être obtenue:


Structure des fichiers

Créez des dépendances dans les classes comme suit:


Déclaration des composants
 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! } 

Limitations


Contrairement à d'autres classes, le ViewController n'est pas créé par nous, mais par la bibliothèque UIKit à l'intérieur de l'implémentation UIStoryboard.instantiateViewController , par conséquent, en utilisant le storyboard, nous ne pouvons pas injecter de dépendances dans les héritiers de l' UIViewController à l'aide de l'initialiseur. Il en va de même avec les héritiers de UIView et UITableViewCell .


Notez que les objets cachés derrière les protocoles sont intégrés dans toutes les classes. C'est l'une des tâches principales de l'implémentation des dépendances - faire des dépendances non pas sur les implémentations, mais sur les interfaces. Cela aidera à l'avenir à fournir différentes implémentations de protocole pour réutiliser ou tester des composants.


Injection de dépendance


Une fois tous les composants du système créés, nous procédons à la connexion des objets entre eux. Dans DITranquillity, le point de départ est DIContainer , qui ajoute l'enregistrement à l'aide de la méthode container.register(...) . Pour séparer les dépendances en parties, DIFramework et DIPart , qui doivent être implémentés. Pour plus de commodité, nous ne créerons qu'une seule classe ApplicationDependency , qui implémentera DIFramework et servira de lieu d'enregistrement pour toutes les dépendances. L'interface DIFramework vous oblige à implémenter une seule méthode - load(container:) .


 class ApplicationDependency: DIFramework { static func load(container: DIContainer) { // registrations will be placed here } } 

Commençons par l'inscription la plus simple qui n'a pas de dépendances - MyNetworking


 container.register(MyNetworking.init) 

Cet enregistrement utilise l'implémentation via l'initialiseur. Malgré le fait que le composant lui-même n'a pas de dépendances, l'initialiseur doit être fourni pour indiquer clairement à la bibliothèque comment créer le composant.


De mĂŞme, enregistrez MyPresenter et MyRouter .


 container.register1(MyPresenter.init) container.register1(MyRouter.init) 

Remarque: Notez que ce n'est pas le register qui est utilisé, mais le register1 . Malheureusement, cela est nécessaire pour indiquer si l'objet a une et une seule dépendance dans l'initialiseur. Autrement dit, s'il existe 0 ou deux dépendances ou plus, il vous suffit d'utiliser le register . Cette restriction est un bogue de Swift version 4.0 et supérieure.


Il est temps d'enregistrer notre ViewController . Il injecte des objets non pas via l'initialiseur, mais directement dans la variable, de sorte que la description de l'enregistrement se révélera un peu plus.


 container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter) 

La syntaxe du formulaire \.presenter .presenter est SwiftKeyPath, grâce à laquelle il est possible d'implémenter de manière concise les dépendances. Étant donné que Router et ViewController cycliquement dépendants l'un de l'autre, vous devez explicitement l'indiquer en utilisant cycle: true . La bibliothèque elle-même peut résoudre ces dépendances sans indication explicite, mais cette exigence a été introduite afin que la personne lisant le graphique comprenne immédiatement qu'il existe des cycles dans la chaîne de dépendances. Notez également que NOT ViewController.init , mais ViewController.self . Cela a été décrit ci-dessus dans la section Limitations .


Il est également nécessaire d'enregistrer l' UIStoryboard aide d'une méthode spéciale.


 container.registerStoryboard(name: "Main") 

Nous avons maintenant décrit le graphique de dépendance entier pour un écran. Mais il n'y a pas encore accès à ce graphique. Vous devez créer un DIContainer qui vous permet d'accéder aux objets qu'il DIContainer .


 static let container: DIContainer = { let container = DIContainer() // 1 container.append(framework: ApplicationDependency.self) // 2 assert(container.validate(checkGraphCycles: true)) // 3 return container }() 

  1. Initialiser le conteneur
  2. Ajoutez-y une description du graphique.
  3. Nous vérifions que nous avons tout fait correctement. En cas d'erreur, l'application ne se bloquera pas lors de la résolution des dépendances, mais immédiatement lors de la création d'un graphique

Ensuite, vous devez faire du conteneur le point de départ de l'application. Pour ce faire, AppDelegate implémentons la méthode didFinishLaunchingWithOptions dans didFinishLaunchingWithOptions au lieu de spécifier Main.storyboard comme point de lancement dans les paramètres du projet.


 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 } 

Lancement


Au premier démarrage, une baisse se produit et la validation échoue pour les raisons suivantes:


  • Le conteneur ne trouvera pas les types Router , Presenter , Networking , car nous n'avons enregistrĂ© que des objets. Si nous voulons donner accès non pas aux implĂ©mentations, mais aux interfaces, nous devons explicitement spĂ©cifier les interfaces
  • Le conteneur ne comprend pas comment rĂ©soudre la dĂ©pendance cyclique, car il est nĂ©cessaire d'indiquer explicitement quels objets ne doivent pas ĂŞtre recréés Ă  chaque rĂ©solution du graphique.

Il est simple de corriger la première erreur - il existe une méthode spéciale qui vous permet de spécifier sous quels protocoles la méthode dans le conteneur est disponible.


 container.register(MyNetworking.init) .as(check: Networking.self) {$0} 

Décrivant l'enregistrement comme suit, nous disons: l'objet MyNetworking est accessible via le protocole Networking . Cela doit être fait pour tous les objets cachés sous les protocoles. {$0} ajoutons pour la vérification correcte des types par le compilateur.


La deuxième erreur est un peu plus compliquée. Il est nécessaire d'utiliser la soi-disant scope , qui décrit à quelle fréquence l'objet est créé et combien il vit. Pour chaque enregistrement participant à une dépendance circulaire, vous devez spécifier une scope égale à objectGraph . Cela indiquera clairement au conteneur que lors de la résolution, il est nécessaire de réutiliser les mêmes objets créés, plutôt que de créer à chaque fois. Ainsi, il s'avère:


 container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter) .lifetime(.objectGraph) container.register1(MyRouter.init) .as(check: Router.self) {$0} .lifetime(.objectGraph) 

Après le redémarrage, le conteneur réussit la validation et notre ViewController s'ouvre avec les dépendances créées. Vous pouvez mettre un point d'arrêt dans viewDidLoad et vous en assurer.


Transition entre écrans


Ensuite, créez deux petites classes, SecondViewController et SecondPresenter , ajoutez SecondViewController au storyboard et créez un Segue entre elles avec l'identifiant "RouteToSecond" , qui vous permet d'ouvrir le deuxième contrôleur à partir du premier.


Ajoutez deux enregistrements supplémentaires à notre ApplicationDependency pour chacune des nouvelles classes:


 container.register(SecondViewController.self) .injection(\.secondPresenter) container.register(SecondPresenter.init) 

.as pas nécessaire de spécifier .as , car nous n'avons pas caché SecondPresenter derrière le protocole, mais utilisons directement l'implémentation. Ensuite, dans la méthode viewDidAppear du premier contrôleur, nous appelons performSegue(withIdentifier: "RouteToSecond", sender: self) , start, le deuxième contrôleur s'ouvre, dans lequel la dépendance secondPresenter doit être apposée. Comme vous pouvez le voir, le conteneur a vu la création d'un deuxième contrôleur à partir d' UIStoryboard et a réussi à supprimer les dépendances.


Conclusion


Cette bibliothèque vous permet de travailler facilement avec les dépendances cycliques, les storyboards et utilise pleinement l'inférence de type automatique dans Swift, qui donne une syntaxe très courte et flexible pour décrire le graphique des dépendances.


Les références


Exemple de code complet dans la bibliothèque github


DITranquillity sur github


Article en anglais

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


All Articles