Dependency Injection: ce este, injectarea dependențelor în iOS și Android

Autor: IT Sectr Publicat: 2026-02-18 Timp de citire: 9 min

Dependency Injection (DI, injectarea dependențelor) — tehnică prin care un obiect primește dependențele sale din exterior, fără a le crea singur. DI este o implementare a principiului IoC (Inversion of Control) și stă la baza Dagger, Hilt și Swinject. Injectarea dependențelor reduce cuplarea codului, simplifică testarea și face arhitectura flexibilă. În Android DI este standard prin Dagger Hilt de la Google, în iOS — prin Swinject sau injectare manuală. Mai multe — în Android DI Guide.

Principalele

  • Dependency Injection — dependențele sunt transmise obiectului din exterior, nu create în interior
  • Inversion of Control — DI implementează principiul IoC, controlul fluxului este transmis containerului
  • Dagger Hilt — standardul DI pentru Android, bazat pe Dagger de la Google
  • Swinject — framework popular DI pentru iOS și Swift
  • Reducerea cuplării — clasa depinde de abstracții, nu de implementări concrete

Ce este Dependency Injection: esența și tipurile DI

Dependency Injection — tehnică prin care un obiect primește dependențe (servicii, repositorii, configurații) prin constructor, setter sau interfață, fără a le crea singur prin new. Scopul DI este reducerea cuplării (coupling) între clase. Dacă o clasă își creează singură dependențele, este legată rigid de implementări concrete, ceea ce complică testarea și modificarea. În DI clasa lucrează cu o abstracție (protocol/interface), iar implementarea concretă este furnizată din exterior.

Trei moduri de injectare — Constructor Injection (prin init/constructor), Setter Injection (prin proprietate/setter), Interface Injection (prin metoda interfeței). Constructor Injection — modul preferat: dependențele sunt vizibile clar în semnătură, obiectul este întotdeauna creat într-o stare validă. Setter Injection este utilizat pentru dependențe opționale cu valoare implicită. Interface Injection — rar, în principal pentru containerele DI.

Tipul DIModulCând se utilizeazăExemplu
ConstructorParametrii inițializatoruluiDependențe obligatoriiinit(service: ServiceProtocol)
PropertyProprietatea claseiDependențe opționalevar service: ServiceProtocol?
MethodParametrul metodeiDependențe temporarefunc doWork(with service: Service)

Containerul DI — bibliotecă care gestionează crearea și ciclul de viață al dependențelor. Containerul conține înregistrarea tipurilor (fiecare tip abstract este mapat la o implementare concretă) și o fabrică pentru crearea obiectelor cu dependențe rezolvate. În Android — Dagger/Hilt, în iOS — Swinject, Needle, Dip. Containerul poate gestiona scopul (Scope): singleton (o instanță per aplicație), scopul funcției (per ecran) sau un obiect nou la fiecare cerere.

Dagger Hilt: DI pentru Android cu generare de cod

Dagger Hilt — strat peste Dagger de la Google, biblioteca standard DI pentru Android. Hilt simplifică Dagger: elimină crearea manuală a componentelor, adaugă @HiltAndroidApp, @AndroidEntryPoint și @Module. Hilt se integrează cu ciclul de viață Android: ViewModel, Activity, Fragment, Service, BroadcastReceiver pot primi dependențe prin adnotări. Generarea de cod are loc la compilare — Dagger generează implementări ale componentelor, ceea ce oferă un overhead runtime zero.

kotlin
// Application class
@HiltAndroidApp
class MyApp : Application()

// Module — definește cum să creeze dependențe
@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 primește dependența prin constructor
@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 activează DI
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels()
}

Componentele Dagger și domeniile de vizibilitate — @Singleton (pentru întreaga aplicație), @ActivityScoped (pentru Activity), @FragmentScoped (pentru Fragment), @ViewModelScoped (pentru ViewModel). Alegerea scopului determină durata de viață a obiectului. @Singleton — o instanță per proces, potrivită pentru OkHttpClient și baza de date. @ActivityScoped — obiectul trăiește cât trăiește Activity, pentru dependențe de ecran. @ViewModelScoped — noutate Hilt 2.45+, obiectul trăiește cât trăiește ViewModel, convenabil pentru scopuri de corutine.

Swinject: DI pentru iOS în Swift

Swinject — framework popular DI pentru iOS cu cod sursă deschis. Swinject oferă Container, Assemblies și diverse scopuri. Spre deosebire de Dagger, Swinject funcționează în runtime — dependențele sunt rezolvate dinamic fără generare de cod. Acest lucru face Swinject mai simplu de configurat, dar dificil de debug-at: eroarea de dependență nerezolvată apare doar în runtime. Swinject suportă Constructor Injection, Property Injection și Method Injection.

swift
import Swinject

// Assembly — grup de înregistrări
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 prin Constructor Injection
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
        }
    }
}

// Configurarea DI în AppDelegate sau App
let assembler = Assembler([NetworkAssembly(), ServiceAssembly()])
let viewModel = assembler.resolver.resolve(ProfileViewModel.self)!

// Property Injection pentru UIKit ViewController
container.register(UserViewController.self) { r in
    let vc = UserViewController()
    vc.viewModel = r.resolve(ProfileViewModel.self)
    return vc
}

Scopurile Swinject — .transient (obiect nou de fiecare dată), .container (singleton pe container), .graph (implicit — obiectul este partajat în cadrul unui singur graf de dependențe). Pentru aplicațiile iOS sunt suficiente .container și .transient. Swinject suportă de asemenea Assembler — gruparea Assembly pentru arhitectură modulară. Pentru teste, Assembly sunt înlocuite cu MockAssembly, ceea ce permite înlocuirea dependențelor fără modificarea codului de producție.

Comparația DI cu Service Locator și injectarea manuală

DI vs Service Locator — ambele pattern-uri rezolvă problema gestionării dependențelor, dar în moduri diferite. DI injectează dependențe în obiect, Service Locator oferă un registru global din care obiectul solicită singur dependențele. DI declară explicit dependențele prin constructor (sau setter). Service Locator ascunde dependențele — ele sunt solicitate în interiorul metodei, ceea ce face semnătura mai puțin informativă. DI se testează mai ușor: este suficient să pui un mock în constructor. Service Locator necesită configurarea registrului global pentru fiecare test.

CaracteristicaDependency InjectionService LocatorInjectare manuală
Evidența dependențelorÎn constructorAscunse în corpul metodeiExplicite
TestareaMock în constructorConfigurarea LocatorMock în constructor
Complexitatea configurăriiNecesită container DIRegistru globalCreare manuală
Overhead runtimeDagger — compile-timeRuntime lookupNu

DI vs injectare manuală — fără container DI, dependențele sunt create manual în fabrici sau AppDelegate. Pentru 5-10 clase injectarea manuală este mai simplă — nu necesită învățarea Dagger sau Swinject. Pentru 50+ clase injectarea manuală devine o problemă: constructori cu 5-6 parametri, ordine complexă de creare, duplicare de cod. Containerul DI automatizează aceste procese și oferă un lifecycle clar. Injectarea manuală fără container — o alegere bună pentru proiecte mici și prototipuri.

Cele mai bune practici Dependency Injection

Constructor Injection — standard. Folosiți întotdeauna Constructor Injection pentru dependențe obligatorii. Acest lucru face dependențele explicite și obiectul întotdeauna gata de lucru. Setter Injection — doar pentru dependențe opționale (de exemplu, delegate sau listener). Interface Injection — nu utilizați, decât dacă scrieți propria bibliotecă DI. Constructor Injection — singurul mod de a garanta că obiectul este creat într-o stare validă.

O clasă — o responsabilitate. Dacă constructorul clasei necesită 5+ parametri, probabil clasa încalcă Single Responsibility Principle. Împărțiți clasa în mai multe cu mai puține dependențe. Semn: dacă scrieți o clasă ServiceManager cu 6 servicii diferite — este un anti-pattern God Object. Extrageți logica de business în Use Cases (Interactori), fiecare cu 1-2 dependențe.

kotlin
// ❌ Rău: 6 dependențe — 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
)

// ✅ Bine: Use Cases cu 1-2 dependențe
class ProfileViewModel @Inject constructor(
    private val loadProfileUseCase: LoadProfileUseCase,
    private val trackAnalyticsUseCase: TrackAnalyticsUseCase
)

Scop și lifecycle — alegeți scopul potrivit pentru fiecare dependență. Singleton: OkHttpClient, baza de date, SharedPreferences. Scopul funcției: repositorii, Use Cases (dacă nu au stare). Transient: Value Objects, DateFormatter, parser-e. Eroarea de scop — problemă frecventă: un singleton care stochează starea ecranului duce la scurgeri de memorie. În Android Hilt @ActivityScoped rezolvă această problemă, în Swinject — .container cu precauție.

Întrebări frecvente

De ce este nevoie de DI dacă putem crea pur și simplu cu new?

new creează o legătură rigidă între clase — nu puteți înlocui implementarea fără a modifica codul. Testarea este dificilă: nu puteți introduce un mock în locul serviciului real. SRP este încălcat: clasa răspunde atât de logica de business, cât și de crearea dependențelor. DI rezolvă aceste probleme prin injectarea dependențelor din exterior și lucrul cu abstracții.

Dagger Hilt sau Koin — ce să alegem pentru Android?

Dagger Hilt — standardul de la Google, DI compile-time cu generare de cod, performanță mai bună și integrare cu Jetpack. Koin — DI runtime, mai simplu de configurat, dar mai lent și cu erori în runtime. Alegeți Hilt pentru proiecte de producție. Koin este potrivit pentru prototipuri și aplicații mici.

Swinject este singurul DI pentru iOS?

Nu. Pentru iOS sunt disponibile: Swinject (runtime, popular), Needle (compile-time de la Uber), Dip (ușor), Weaver (bazat pe Sourcery). Apple nu oferă un container DI încorporat, dar injectarea manuală prin init este o practică standard. Pentru SwiftUI este adesea suficient DI manual prin Environment sau @StateObject fără biblioteci externe.

Se poate folosi DI fără framework?

Da. Injectarea manuală prin constructor — este DI fără framework. Service Locator — o alternativă fără framework. Fabricile și Factory Method — de asemenea o formă de DI. Framework-ul (Dagger, Swinject) automatizează înregistrarea de rutină și rezolvarea dependențelor, dar pentru 10-20 de clase DI manual este suficient.

DI este un pattern sau un principiu?

DI — este o tehnică (pattern) care implementează principiul Inversion of Control. Spre deosebire de pattern-urile GoF, DI nu are o structură strictă de 3-4 clase. DI este un mod de organizare a dependențelor, nu un pattern de proiectare. Containerele DI (Dagger, Swinject) sunt framework-uri care automatizează această tehnică.

Concluzii

  • DI — tehnică de injectare a dependențelor din exterior prin constructor, setter sau metodă
  • Dagger Hilt — standard DI pentru Android cu generare de cod compile-time și @HiltViewModel
  • Swinject — DI runtime pentru iOS cu Container, Assembly și scopuri
  • Constructor Injection — modul preferat pentru dependențe obligatorii
  • Scop — Singleton pentru servicii fără stare, scopul funcției pentru dependențe de ecran
  • Testarea — DI simplifică înlocuirea dependențelor cu mock fără modificarea codului

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și