Dependency Injection: vad är det, dependency injection i iOS och Android

Författare: IT Sectr Publicerad: 2026-02-18 Lästid: 9 min

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 — beroenden överförs till objektet utifrån, skapas inte internt
  • Inversion of Control — DI implementerar IoC-principen, flödeskontrollen överförs till containern
  • Dagger Hilt — DI-standard för Android, baserad på Dagger från Google
  • Swinject — populärt DI-ramverk för iOS och Swift
  • Minskad koppling — klassen beror på abstraktioner, inte på konkreta implementationer

Vad är Dependency Injection: essens och typer av DI

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-typSättNär att användaExempel
ConstructorInitieringsparametrarObligatoriska beroendeninit(service: ServiceProtocol)
PropertyKlassenhetValfria beroendenvar service: ServiceProtocol?
MethodMetodparameterTillfälliga beroendenfunc 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: DI för Android med kodgenerering

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.

kotlin
// 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: DI för iOS i Swift

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.

swift
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.

Jämförelse av DI med Service Locator och manuell injektion

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.

EgenskapDependency InjectionService LocatorManuell injektion
Tydlighet av beroendenI konstruktornDolda i metodkroppenExplicita
TestningMock i konstruktorKonfigurera LocatorMock i konstruktor
Komplexitet i konfigurationKräver DI-containerGlobalt registerManuellt skapande
Runtime-overheadDagger — compile-timeRuntime lookupIngen

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.

Bästa praxis för Dependency Injection

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.

kotlin
// ❌ 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

Varför behövs DI om man bara kan skapa med new?

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 eller Koin — vad väljer man för Android?

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.

Är Swinject den enda DI:n för iOS?

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.

Kan DI användas utan ramverk?

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.

Är DI ett mönster eller en princip?

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

  • DI — teknik för dependency injection utifrån via konstruktor, setter eller metod
  • Dagger Hilt — DI-standard för Android med compile-time kodgenerering och @HiltViewModel
  • Swinject — runtime DI för iOS med Container, Assembly och omfattningar
  • Constructor Injection — föredraget sätt för obligatoriska beroenden
  • Omfattning — Singleton för tillståndslösa tjänster, funktionsomfattning för skärmberoenden
  • Testning — DI förenklar utbyte av beroenden mot mock utan kodändring

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.

Diskutera projektet

Läs också