Dependency Injection : qu'est-ce que c'est, injection de dépendances dans iOS et Android

Auteur : IT Sectr Publié le : 2026-02-18 Temps de lecture : 9 min

Injection de dépendances (DI) est une technique par laquelle un objet reçoit ses dépendances de l'extérieur plutôt que de les créer lui-même. La DI est une implémentation du principe IoC (Inversion de Contrôle) et est à la base de Dagger, Hilt et Swinject. L'injection de dépendances réduit le couplage du code, simplifie les tests et rend l'architecture flexible. Sous Android, la DI est standard via Dagger Hilt de Google ; sous iOS, via Swinject ou l'injection manuelle. En savoir plus dans le Guide DI Android.

Points clés

  • Injection de dépendances — les dépendances sont passées à l'objet de l'extérieur, non créées en interne
  • Inversion de Contrôle — la DI implémente le principe IoC, le flux de contrôle est transféré au conteneur
  • Dagger Hilt — le standard DI pour Android, basé sur Dagger de Google
  • Swinject — un framework DI populaire pour iOS et Swift
  • Couplage réduit — une classe dépend d'abstractions, pas d'implémentations concrètes

Qu'est-ce que Dependency Injection : essence et types de DI

Injection de dépendances est une technique par laquelle un objet reçoit des dépendances (services, dépôts, configurations) via un constructeur, un setter ou une interface, plutôt que de les créer lui-même avec new. Le but de la DI est de réduire le couplage entre les classes. Si une classe crée elle-même ses dépendances, elle est fortement liée à des implémentations spécifiques, ce qui rend les tests et les modifications difficiles. Avec la DI, la classe travaille avec une abstraction (protocole/interface) et l'implémentation concrète est fournie de l'extérieur.

Trois méthodes d'injection — Injection par constructeur (via init/constructor), Injection par setter (via propriété/setter), Injection par interface (via une méthode d'interface). L'injection par constructeur est la méthode préférée : les dépendances sont clairement visibles dans la signature et l'objet est toujours créé dans un état valide. L'injection par setter est utilisée pour les dépendances optionnelles avec valeur par défaut. L'injection par interface est rare, principalement pour les conteneurs DI.

Type de DIMéthodeQuand l'utiliserExemple
ConstructeurParamètres de l'initialiseurDépendances obligatoiresinit(service: ServiceProtocol)
PropriétéPropriété de classeDépendances optionnellesvar service: ServiceProtocol?
MéthodeParamètre de méthodeDépendances temporairesfunc doWork(with service: Service)

Conteneur DI — une bibliothèque qui gère la création et le cycle de vie des dépendances. Le conteneur contient des enregistrements de types (chaque type abstrait mappé à une implémentation concrète) et une fabrique pour créer des objets avec des dépendances résolues. Sous Android — Dagger/Hilt, sous iOS — Swinject, Needle, Dip. Le conteneur peut gérer la portée : singleton (une instance par application), portée de fonctionnalité (par écran) ou un nouvel objet à chaque demande.

Dagger Hilt : DI pour Android avec génération de code

Dagger Hilt est une surcouche de Dagger de Google, la bibliothèque DI standard pour Android. Hilt simplifie Dagger : il supprime la création manuelle de composants, ajoute @HiltAndroidApp, @AndroidEntryPoint et @Module. Hilt s'intègre au cycle de vie Android : ViewModel, Activity, Fragment, Service, BroadcastReceiver peuvent recevoir des dépendances via des annotations. La génération de code se produit à la compilation — Dagger génère des implémentations de composants, ce qui donne une surcharge d'exécution nulle.

kotlin
// Classe d'application
@HiltAndroidApp
class MyApp : Application()

// Module — définit comment créer les dépendances
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder().build()

    @Provides
    @Singleton
    fun provideApiService(client: OkHttpClient): ApiService {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com")
            .client(client)
            .build()
            .create(ApiService::class.java)
    }
}

// ViewModel reçoit la dépendance via le constructeur
@HiltViewModel
class MainViewModel @Inject constructor(
    private val apiService: ApiService
) : ViewModel() {
    private val _state = MutableStateFlow(MainState.Loading)
    val state: StateFlow<MainState> = _state.asStateFlow()

    fun loadData() {
        viewModelScope.launch {
            _state.value = MainState.Success(apiService.getData())
        }
    }
}

// Activity — @AndroidEntryPoint active la DI
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels()
}

Composants et portées Dagger — @Singleton (toute l'application), @ActivityScoped (par Activity), @FragmentScoped (par Fragment), @ViewModelScoped (par ViewModel). Le choix de la portée détermine la durée de vie de l'objet. @Singleton — une instance par processus, adapté à OkHttpClient et aux bases de données. @ActivityScoped — l'objet vit tant que l'Activity vit, pour les dépendances au niveau de l'écran. @ViewModelScoped — nouveauté de Hilt 2.45+, l'objet vit tant que le ViewModel vit, pratique pour les portées de coroutines.

Swinject : DI pour iOS en Swift

Swinject est un framework DI open source populaire pour iOS. Swinject fournit Container, Assemblies et diverses portées. Contrairement à Dagger, Swinject fonctionne à l'exécution — les dépendances sont résolues dynamiquement sans génération de code. Cela rend Swinject plus facile à configurer, mais le débogage est plus difficile : une erreur de dépendance non résolue n'apparaît qu'à l'exécution. Swinject prend en charge l'injection par constructeur, l'injection par propriété et l'injection par méthode.

swift
import Swinject

// Assembly — un groupe d'enregistrements
class NetworkAssembly: Assembly {
    func assemble(container: Container) {
        container.register(NetworkServiceProtocol.self) { _ in
            NetworkService()
        }.inObjectScope(.container) // singleton

        container.register(UserRepositoryProtocol.self) { r in
            UserRepository(
                networkService: r.resolve(NetworkServiceProtocol.self)!
            )
        }
    }
}

// ViewModel via l'injection par constructeur
class ProfileViewModel: ObservableObject {
    private let repository: UserRepositoryProtocol

    init(repository: UserRepositoryProtocol) {
        self.repository = repository
    }

    @Published var user: User?
    func loadUser() {
        repository.fetchUser { [weak self] user in
            self?.user = user
        }
    }
}

// Configuration de la DI dans AppDelegate ou App
let assembler = Assembler([NetworkAssembly(), ServiceAssembly()])
let viewModel = assembler.resolver.resolve(ProfileViewModel.self)!

// Injection par propriété pour UIKit ViewController
container.register(UserViewController.self) { r in
    let vc = UserViewController()
    vc.viewModel = r.resolve(ProfileViewModel.self)
    return vc
}

Portées Swinject — .transient (nouvel objet à chaque fois), .container (singleton par conteneur), .graph (par défaut — l'objet est partagé au sein d'un seul graphe de dépendances). Pour les applications iOS, .container et .transient suffisent. Swinject prend également en charge Assembler — le regroupement d'Assembly pour une architecture modulaire. Pour les tests, Assembly est remplacé par MockAssembly, permettant la substitution de dépendances sans modifier le code de production.

Comparaison de la DI avec Service Locator et l'injection manuelle

DI vs Service Locator — les deux modèles résolvent la gestion des dépendances, mais différemment. La DI injecte les dépendances dans l'objet ; Service Locator fournit un registre global à partir duquel l'objet demande lui-même les dépendances. La DI déclare explicitement les dépendances via le constructeur (ou le setter). Service Locator cache les dépendances — elles sont demandées à l'intérieur de la méthode, rendant la signature moins informative. La DI est plus facile à tester : il suffit de passer un mock au constructeur. Service Locator nécessite la configuration du registre global pour chaque test.

CaractéristiqueInjection de dépendancesService LocatorInjection manuelle
Visibilité des dépendancesDans le constructeurCachées dans le corps de la méthodeExplicites
TestsMock dans le constructeurConfiguration du LocatorMock dans le constructeur
Complexité de configurationNécessite un conteneur DIRegistre globalCréation manuelle
Surcharge d'exécutionDagger — compilationRecherche à l'exécutionAucune

DI vs injection manuelle — sans conteneur DI, les dépendances sont créées manuellement dans des fabriques ou AppDelegate. Pour 5 à 10 classes, l'injection manuelle est plus simple — pas besoin d'apprendre Dagger ou Swinject. Pour 50+ classes, l'injection manuelle devient problématique : constructeurs avec 5 à 6 paramètres, ordre de création complexe, duplication de code. Un conteneur DI automatise ces processus et fournit une gestion claire du cycle de vie. L'injection manuelle sans conteneur est un bon choix pour les petits projets et les prototypes.

Bonnes pratiques de l'injection de dépendances

Injection par constructeur — standard. Utilisez toujours l'injection par constructeur pour les dépendances obligatoires. Cela rend les dépendances explicites et l'objet toujours prêt à fonctionner. Injection par setter — uniquement pour les dépendances optionnelles (par exemple, delegate ou listener). Injection par interface — ne l'utilisez pas sauf si vous écrivez votre propre bibliothèque DI. L'injection par constructeur est le seul moyen de garantir qu'un objet est créé dans un état valide.

Une classe — une responsabilité. Si le constructeur d'une classe nécessite 5+ paramètres, la classe viole probablement le principe de responsabilité unique. Divisez la classe en plusieurs avec moins de dépendances. Un signe : si vous écrivez une classe ServiceManager avec 6 services différents — c'est l'anti-modèle God Object. Extrayez la logique métier dans des Use Cases (Interactors), chacun avec 1 à 2 dépendances.

kotlin
// ❌ Mauvais : 6 dépendances — God Object
class ProfileViewModel @Inject constructor(
    private val api: ApiService,
    private val db: Database,
    private val analytics: Analytics,
    private val prefs: Preferences,
    private val location: LocationProvider,
    private val notification: NotificationManager
)

// ✅ Bon : Use Cases avec 1-2 dépendances
class ProfileViewModel @Inject constructor(
    private val loadProfileUseCase: LoadProfileUseCase,
    private val trackAnalyticsUseCase: TrackAnalyticsUseCase
)

Portée et cycle de vie — choisissez la bonne portée pour chaque dépendance. Singletons : OkHttpClient, base de données, SharedPreferences. Portée de fonctionnalité : dépôts, Use Cases (s'ils sont sans état). Transient : Value Objects, DateFormatter, analyseurs. Les erreurs de portée sont un problème courant : un singleton stockant l'état de l'écran provoque des fuites. Sous Android, @ActivityScoped de Hilt résout ce problème ; dans Swinject, utilisez .container avec prudence.

Questions fréquentes

Pourquoi ai-je besoin de la DI si je peux simplement utiliser new ?

new crée un couplage fort entre les classes — vous ne pouvez pas changer l'implémentation sans modifier le code. Les tests deviennent difficiles : vous ne pouvez pas injecter un mock à la place d'un vrai service. Le SRP est violé : la classe est responsable à la fois de la logique métier et de la création des dépendances. La DI résout ces problèmes en injectant les dépendances de l'extérieur et en travaillant avec des abstractions.

Dagger Hilt ou Koin — lequel choisir pour Android ?

Dagger Hilt est le standard de Google, DI à la compilation avec génération de code, meilleures performances et intégration Jetpack. Koin est DI à l'exécution, plus facile à configurer, mais plus lent et avec des erreurs à l'exécution. Choisissez Hilt pour les projets de production. Koin convient aux prototypes et aux petites applications.

Swinject est-il la seule option DI pour iOS ?

Non. Pour iOS sont disponibles : Swinject (exécution, populaire), Needle (compilation par Uber), Dip (léger), Weaver (basé sur Sourcery). Apple ne fournit pas de conteneur DI intégré, mais l'injection manuelle via init est une pratique standard. Pour SwiftUI, la DI manuelle via Environment ou @StateObject sans bibliothèques externes est souvent suffisante.

Puis-je utiliser la DI sans framework ?

Oui. L'injection manuelle via le constructeur est de la DI sans framework. Service Locator est une alternative sans framework. Les fabriques et Factory Method sont aussi des formes de DI. Un framework (Dagger, Swinject) automatise l'enregistrement et la résolution des dépendances, mais pour 10 à 20 classes, la DI manuelle suffit.

La DI est-elle un modèle ou un principe ?

La DI est une technique (template) qui implémente le principe d'Inversion de Contrôle. Contrairement aux modèles GoF, la DI n'a pas de structure stricte de 3 à 4 classes. La DI est une façon d'organiser les dépendances, pas un modèle de conception. Les conteneurs DI (Dagger, Swinject) sont des frameworks qui automatisent cette technique.

Résumé

  • DI — une technique d'injection de dépendances de l'extérieur via constructeur, setter ou méthode
  • Dagger Hilt — le standard DI pour Android avec génération de code à la compilation et @HiltViewModel
  • Swinject — DI à l'exécution pour iOS avec Container, Assembly et portées
  • Injection par constructeur — la méthode préférée pour les dépendances obligatoires
  • Portée — Singleton pour les services sans état, portée de fonctionnalité pour les dépendances d'écran
  • Tests — la DI simplifie le remplacement des dépendances par des mocks sans modifier le code

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi