Dependency Injection (DI, dependency injection) — teknik där ett objekt får sina beroenden utifrån, istället för att skapa dem själv. DI är en implementering av IoC-principen (Inversion of Control) och ligger till grund för Dagger, Hilt och Swinject. Dependency injection minskar kodkoppling, förenklar testning och gör arkitekturen flexibel. I Android är DI standard via Dagger Hilt från Google, i iOS — via Swinject eller manuell injektion. Mer — i Android DI Guide.
Huvudpunkter
Dependency Injection — teknik där ett objekt tar emot beroenden (tjänster, datalager, konfigurationer) via konstruktor, setter eller gränssnitt, istället för att skapa dem själv med new. Målet med DI är att minska kopplingen (coupling) mellan klasser. Om en klass själv skapar sina beroenden är den hårt bunden till konkreta implementationer, vilket försvårar testning och modifiering. Vid DI arbetar klassen med en abstraktion (protocol/interface) och den konkreta implementationen tillhandahålls utifrån.
Tre sätt att injicera — Constructor Injection (via init/constructor), Setter Injection (via egenskap/setter), Interface Injection (via gränssnittsmetod). Constructor Injection — det föredragna sättet: beroenden är tydligt synliga i signaturen, objektet skapas alltid i ett giltigt tillstånd. Setter Injection används för valfria beroenden med standardvärde. Interface Injection — sällan, främst för DI-containrar.
| DI-typ | Sätt | När att använda | Exempel |
|---|---|---|---|
| Constructor | Initieringsparametrar | Obligatoriska beroenden | init(service: ServiceProtocol) |
| Property | Klassenhet | Valfria beroenden | var service: ServiceProtocol? |
| Method | Metodparameter | Tillfälliga beroenden | func doWork(with service: Service) |
DI-container — bibliotek som hanterar skapande och livscykel av beroenden. Containern innehåller registrering av typer (varje abstrakt typ mappas till en konkret implementation) och en fabrik för att skapa objekt med lösta beroenden. I Android — Dagger/Hilt, i iOS — Swinject, Needle, Dip. Containern kan hantera omfattning (Scope): singleton (en instans per applikation), funktionsomfattning (per skärm) eller nytt objekt vid varje begäran.
Dagger Hilt — lager ovanpå Dagger från Google, standard DI-bibliotek för Android. Hilt förenklar Dagger: tar bort manuellt skapande av komponenter, lägger till @HiltAndroidApp, @AndroidEntryPoint och @Module. Hilt integreras med Android-livscykeln: ViewModel, Activity, Fragment, Service, BroadcastReceiver kan ta emot beroenden via annoteringar. Kodgenerering sker vid kompilering — Dagger genererar implementationer av komponenter, vilket ger noll runtime-overhead.
// Application class
@HiltAndroidApp
class MyApp : Application()
// Module — definierar hur beroenden skapas
@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 tar emot beroende via konstruktor
@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 aktiverar DI
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
}
Dagger-komponenter och synlighetsomfattningar — @Singleton (för hela applikationen), @ActivityScoped (för Activity), @FragmentScoped (för Fragment), @ViewModelScoped (för ViewModel). Valet av omfattning bestämmer objektets livslängd. @Singleton — en instans per process, lämplig för OkHttpClient och databas. @ActivityScoped — objektet lever så länge Activity lever, för skärmberoenden. @ViewModelScoped — nyhet i Hilt 2.45+, objektet lever så länge ViewModel lever, bekvämt för coroutine-omfattningar.
Swinject — populärt DI-ramverk för iOS med öppen källkod. Swinject tillhandahåller Container, Assemblies och olika omfattningar. Till skillnad från Dagger fungerar Swinject i runtime — beroenden löses dynamiskt utan kodgenerering. Detta gör Swinject enklare att konfigurera, men svårare att felsöka: ett fel med olöst beroende visas först i runtime. Swinject stödjer Constructor Injection, Property Injection och Method Injection.
import Swinject
// Assembly — registreringsgrupp
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 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
}
}
}
// DI-konfiguration i AppDelegate eller App
let assembler = Assembler([NetworkAssembly(), ServiceAssembly()])
let viewModel = assembler.resolver.resolve(ProfileViewModel.self)!
// Property Injection för UIKit ViewController
container.register(UserViewController.self) { r in
let vc = UserViewController()
vc.viewModel = r.resolve(ProfileViewModel.self)
return vc
}
Swinject-omfattningar — .transient (nytt objekt varje gång), .container (singleton per container), .graph (standard — objektet delas inom en beroendegraf). För iOS-applikationer räcker .container och .transient. Swinject stödjer också Assembler — gruppering av Assembly för modulär arkitektur. För tester ersätts Assembly med MockAssembly, vilket möjliggör utbyte av beroenden utan att ändra produktionskoden.
DI vs Service Locator — båda mönstren löser problemet med beroendehantering, men på olika sätt. DI injicerar beroenden i objektet, Service Locator tillhandahåller ett globalt register där objektet själv hämtar beroenden. DI deklarerar explicit beroenden via konstruktor (eller setter). Service Locator döljer beroenden — de hämtas inuti metoden, vilket gör signaturen mindre informativ. DI är lättare att testa: det räcker att sätta en mock i konstruktorn. Service Locator kräver konfiguration av det globala registret för varje test.
| Egenskap | Dependency Injection | Service Locator | Manuell injektion |
|---|---|---|---|
| Tydlighet av beroenden | I konstruktorn | Dolda i metodkroppen | Explicita |
| Testning | Mock i konstruktor | Konfigurera Locator | Mock i konstruktor |
| Komplexitet i konfiguration | Kräver DI-container | Globalt register | Manuellt skapande |
| Runtime-overhead | Dagger — compile-time | Runtime lookup | Ingen |
DI vs manuell injektion — utan DI-container skapas beroenden manuellt i fabriker eller AppDelegate. För 5-10 klasser är manuell injektion enklare — kräver inte inlärning av Dagger eller Swinject. För 50+ klasser blir manuell injektion ett problem: konstruktorer med 5-6 parametrar, komplex skaparordning, kodduplicering. DI-containern automatiserar dessa processer och ger en tydlig livscykel. Manuell injektion utan container — ett bra val för små projekt och prototyper.
Constructor Injection — standard. Använd alltid Constructor Injection för obligatoriska beroenden. Detta gör beroenden explicita och objektet alltid redo för arbete. Setter Injection — endast för valfria beroenden (t.ex. delegate eller listener). Interface Injection — använd inte, om du inte skriver ditt eget DI-bibliotek. Constructor Injection — det enda sättet att garantera att objektet skapas i ett giltigt tillstånd.
En klass — ett ansvar. Om en klass konstruktor kräver 5+ parametrar bryter klassen troligen mot Single Responsibility Principle. Dela upp klassen i flera med färre beroenden. Tecken: om du skriver en klass ServiceManager med 6 olika tjänster — är detta anti-mönstret God Object. Flytta affärslogiken till Use Cases (Interactors), var och en med 1-2 beroenden.
// ❌ Dåligt: 6 beroenden — 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
)
// ✅ Bra: Use Cases med 1-2 beroenden
class ProfileViewModel @Inject constructor(
private val loadProfileUseCase: LoadProfileUseCase,
private val trackAnalyticsUseCase: TrackAnalyticsUseCase
)
Omfattning och livscykel — välj rätt omfattning för varje beroende. Singletons: OkHttpClient, databas, SharedPreferences. Funktionsomfattning: datalager, Use Cases (om de inte har tillstånd). Transient: Value Objects, DateFormatter, parsers. Omfattningsfel — ett vanligt problem: en singleton som lagrar skärmtillstånd leder till minnesläckor. I Android Hilt löser @ActivityScoped detta problem, i Swinject — .container med försiktighet.
Vanliga frågor
New skapar en stark koppling mellan klasser — du kan inte byta implementation utan att ändra koden. Testning är svårt: du kan inte sätta en mock istället för den riktiga tjänsten. SRP bryts: klassen ansvarar för både affärslogik och skapande av beroenden. DI löser dessa problem genom att injicera beroenden utifrån och arbeta med abstraktioner.
Dagger Hilt — standard från Google, compile-time DI med kodgenerering, bättre prestanda och integration med Jetpack. Koin — runtime DI, enklare att konfigurera, men långsammare och med runtime-fel. Välj Hilt för produktionsprojekt. Koin passar för prototyper och små applikationer.
Nej. För iOS finns: Swinject (runtime, populär), Needle (compile-time från Uber), Dip (lättvikt), Weaver (baserad på Sourcery). Apple tillhandahåller ingen inbyggd DI-container, men manuell injektion via init är standardpraxis. För SwiftUI räcker ofta manuell DI via Environment eller @StateObject utan externa bibliotek.
Ja. Manuell injektion via konstruktor — detta är DI utan ramverk. Service Locator — ett alternativ utan ramverk. Fabriker och Factory Method — också en form av DI. Ramverket (Dagger, Swinject) automatiserar rutinmässig registrering och lösning av beroenden, men för 10-20 klasser räcker manuell DI.
DI — är en teknik (mönster) som implementerar principen Inversion of Control. Till skillnad från GoF-mönster har DI ingen strikt struktur med 3-4 klasser. DI är ett sätt att organisera beroenden, inte ett designmönster. DI-containrar (Dagger, Swinject) är ramverk som automatiserar denna teknik.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också