Injection de dépendances en Swift : guide pratique pour iOS

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : Pourquoi l'injection de dépendances est une nécessité

\n

Dans le développement moderne pour iOS et macOS, la propreté et la testabilité du code deviennent primordiales. L'un des principes clés permettant d'atteindre ces objectifs est l'injection de dépendances (DI). Sans DI, les classes deviennent souvent "fortement couplées" (tightly coupled), ce qui rend leur réutilisation, leur modification et, surtout, l'écriture de tests unitaires difficiles.

\n

En pratique, l'injection de dépendances en Swift signifie qu'un objet reçoit ses dépendances de l'extérieur, plutôt que de les créer lui-même. Cela peut sembler un changement mineur, mais cela transforme radicalement l'architecture de l'application. Dans cet article, nous allons détailler les principaux modèles de DI en Swift : Constructor Injection, Property Injection et l'utilisation d'un conteneur DI avec l'exemple de Swinject. Chaque méthode sera illustrée par des exemples pratiques pour iOS/macOS.

\n

Prêt à rendre votre code plus flexible et testable ? Alors commençons.

\n\n

1. Constructor Injection : La méthode la plus fiable

\n

Constructor Injection est l'injection de dépendances via l'initialiseur (init) de la classe. Cette méthode est considérée comme la plus préférable car elle garantit que l'objet est toujours dans un état valide : toutes ses dépendances sont obligatoires et immuables.

\n

Prenons un exemple. Supposons que nous ayons une classe NetworkService qui gère les requêtes HTTP, et une classe UserViewModel qui utilise ce service.

\n
// Protocole pour l'abstraction\nprotocol NetworkServiceProtocol {\n    func fetchUsers() async throws -> [User]\n}\n\n// Implémentation réelle\nclass RealNetworkService: NetworkServiceProtocol {\n    func fetchUsers() async throws -> [User] {\n        // Requête HTTP réelle\n        return []\n    }\n}\n\n// ViewModel avec Constructor Injection\nclass UserViewModel {\n    private let networkService: NetworkServiceProtocol\n\n    // La dépendance est transmise via l'initialiseur\n    init(networkService: NetworkServiceProtocol) {\n        self.networkService = networkService\n    }\n\n    func loadUsers() async {\n        do {\n            let users = try await networkService.fetchUsers()\n            print("Chargé \\(users.count) utilisateurs")\n        } catch {\n            print("Erreur : \\(error)")\n        }\n    }\n}\n\n// Utilisation\nlet service = RealNetworkService()\nlet viewModel = UserViewModel(networkService: service)\n
\n

Avantages :

\n
    \n
  • La dépendance est explicitement déclarée et obligatoire.
  • \n
  • L'objet ne peut pas être créé sans dépendances.
  • \n
  • Idéal pour les tests unitaires : facile de remplacer l'implémentation réelle par un Mock.
  • \n
\n

Inconvénients :

\n
    \n
  • Si les dépendances sont nombreuses, l'initialiseur devient volumineux.
  • \n
  • Ne convient pas pour les dépendances optionnelles.
  • \n
\n

Cette méthode est le "gold standard" de la DI. Utilisez-la partout où c'est possible.

\n\n

2. Property Injection : Flexibilité pour les dépendances optionnelles

\n

Property Injection est l'injection de dépendances via les propriétés (property) de l'objet. Elle est généralement utilisée lorsque la dépendance n'est pas obligatoire pour le fonctionnement de l'objet ou lorsqu'elle ne peut pas être transmise via l'initialiseur (par exemple, dans Storyboard/UIKit).

\n

Un exemple classique est le ViewController dans iOS, qui est créé à partir d'un Storyboard. Vous ne pouvez pas modifier son init, vous utilisez donc une propriété.

\n
protocol LoggerProtocol {\n    func log(message: String)\n}\n\nclass ConsoleLogger: LoggerProtocol {\n    func log(message: String) {\n        print("LOG : \\(message)")\n    }\n}\n\nclass MyViewController: UIViewController {\n    // Dépendance optionnelle\n    var logger: LoggerProtocol?\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        logger?.log(message: "View did load")\n        // Si logger n'est pas défini, rien ne se produit\n    }\n}\n\n// Configuration (par exempl                

Blogs

Book Recommendations