Injection de dépendances et découplage dans une application Swift iOS

Il n'y a pas longtemps nous avons discuté de l'injection de dépendance et du découplage dans leur globalité. C'est un principe que j'essaie d'appliquer à tous mes projets.

J'ai donc tenté la chose dans mes applications iOS. Le gros problème est la structure même d'une application iOS : le centre du projet (et de l'application du coup) sont les ViewController qui sont, pour la plupart des cas, liés aux objets créés dans interface builder. Difficile donc, d'injecter mes dépendances à l'initialisation de mon contrôleur.

Une grande partie de la navigation se fait par les segue, là aussi tout est géré par le système et l'injection de dépendance ainsi que le découplage semble impossible.

Découplage du code métier

Pour rappel, le découplage consiste à réduire au maximum les dépendances de notre code aux différents éléments extérieurs (framework, plateforme, etc.).

Le but étant :

  • des changements de structure simplifiés
  • des tests plus simples à écrire
  • du code simplifié
  • du code plus réutilisable

Dans mon projet iOS, je vois l'ensemble comme un framework. Comment pourrais-je découpler mon code de façon à ce que le code métier soit réutilisable si je devais changer de framework ? Pour une application web en Swift, par exemple.

Expérimentations.

Je suis une règle simple dans ma recherche du découplage : éviter au maximum de mettre du code métier dans des fichiers propres à un projet iOS. J'en viens à me construire une micro-application dans l'application.

Je tiens à préciser que ce que je fais ici n'est pas forcément la meilleure solution. J'explique juste la méthode que j'utilise en ce moment. Libre à chacun de juger si la méthode lui convient ou s'il faut en améliorer certains aspects.

Voici un exemple de structure dans un de mes projets. Il s'agit d'une application de conversion de devises.

Class/
    RatesProvider/
        RatesProvider.swift
        FixerIOProvider.swift
        YahooProvider.swift
    Converter.swift
Controller/
View/
Model/
AppDelegate.swift

Pour tout ce qui est des dossiers Controller, View,Model, et du fichier AppDelegate.swift je pense que vous voyez très bien de quoi il s'agit et ce n'est pas vraiment le sujet ici.

Ce qui nous intéresse, c'est le dossier Class. C'est là que je range mes classes métiers. Dans ces fichiers, il n'y a aucune référence à du code "Swift iOS". Par exemple, je n'y importe pas UIKit. Si je copie ce dossier dans un projet Vapor il fonctionnera.

Pour ce qui est des interactions entre les classes métier, je pratique intensivement le découplage par injection de dépendances. Exemple :

Pour convertir (Converter()) une valeur USD en valeur EUR, j'ai besoin de connaitre les taux de change pour cette paire de devises : Je vais utiliser un service — provider — qui me fournira ces valeurs régulièrement mises à jour. Nouvelle responsabilité, nouvelle classe. (FixerIOProvider())

Je pourrais éventuellement changer de provider à l'avenir : Je définis un contrat (ou protocol en Swift) qui va définir les modalités pour qu'un provider puisse être utilisé dans mon application (RatesProvider()). Ma classe Converter demandera à utiliser des objets respectant le protocole RatesProvider. Je fais en sorte que FixerIOProvider implémente RatesProvider.

Effectivement, je finis par vouloir changer de provider ; de FixerIO je passe à Yahoo Finances. J'écris la classe YahooProvider qui implémente le protocole RatesProvider et je la passe a Converter() en lieu et place de FixerIOProvider(). Aucun autre changement n'est nécessaire.

Mes classes doivent au maximum être indépendantes les unes des autres. J'ai plus de fichiers, mais je gagne en flexibilité et mon code est bien plus agréable à lire, plus simple à comprendre.

Quid du container d'injection de dépendance ?

Pour beaucoup, l'injection de dépendance ne va pas sans container. Et il est vrai que ça a des avantages. Mais je n'en utilise pas dans mes projets iOS.

Je n'ai malheureusement pas trouvé de solution qui me convienne pour l'instant. Ce qui existe ne me plait pas vraiment. Ce que j'ai codé n'est pas top non plus.

Donc en attendant de trouver mieux, j'utilise le Factory Pattern.

Conclusion : découpler une application iOS, c'est possible !

Voilà ce que je retiens. Le but n'est pas de réinventer le fonctionnement d'interface builder, l'appel aux contrôleurs et tout le reste. Apple a bien assez d'ingénieurs pour ça.

Nous ce que l'on veut, c'est séparer notre code métier du code projet iOS exactement comme on l'aurait fait avec Symfony si on codait en PHP. Et ça, c’est possible.

Bien-sûr, comme on l'a vu, il y a encore une bonne marge de progression, mais je continue à chercher.