Dependency Injection: шта је то, убризгавање зависности у iOS и Android

Аутор: IT Sectr Објављено: 2026-02-18 Време читања: 9 мин

Dependency Injection (DI, убризгавање зависности) — техника при којој објекат прима своје зависности споља, уместо да их сам креира. DI је имплементација принципа IoC (Inversion of Control) и лежи у основи Dagger, Hilt и Swinject. Убризгавање зависности смањује повезаност кода, поједностављује тестирање и чини архитектуру флексибилном. У Android DI је стандард кроз Dagger Hilt од Google-а, у iOS — кроз Swinject или ручно убризгавање. Више — у Android DI Guide.

Главно

  • Dependency Injection — зависности се преносе објекту споља, а не креирају унутра
  • Inversion of Control — DI имплементира принцип IoC, управљање током се преноси контејнеру
  • Dagger Hilt — стандард DI за Android, заснован на Dagger од Google-а
  • Swinject — популарни DI оквир за iOS и Swift
  • Смањење повезаности — класа зависи од апстракција, а не од конкретних имплементација

Шта је Dependency Injection: суштина и врсте DI

Dependency Injection — техника при којој објекат прима зависности (сервисе, репозиторијуме, конфигурације) кроз конструктор, сетер или интерфејс, уместо да их сам креира кроз new. Циљ DI је смањење повезаности (coupling) између класа. Ако класа сама креира зависности, она је чврсто везана за конкретне имплементације, што отежава тестирање и модификацију. Код DI класа ради са апстракцијом (protocol/interface), а конкретна имплементација се доставља споља.

Три начина убризгавања — Constructor Injection (кроз init/constructor), Setter Injection (кроз својство/сетер), Interface Injection (кроз метод интерфејса). Constructor Injection — пожељни начин: зависности су јасно видљиве у потпису, објекат се увек креира у валидном стању. Setter Injection се користи за опционе зависности са подразумеваном вредношћу. Interface Injection — ретко, углавном за DI контејнере.

Врста DIНачинКада користитиПример
ConstructorПараметри иницијализатораОбавезне зависностиinit(service: ServiceProtocol)
PropertyСвојство класеОпционе зависностиvar service: ServiceProtocol?
MethodПараметар методеПривремене зависностиfunc doWork(with service: Service)

DI контејнер — библиотека која управља креирањем и животним циклусом зависности. Контејнер садржи регистрацију типова (сваки апстрактни тип је мапиран на конкретну имплементацију) и фабрику за креирање објеката са решеним зависностима. У Android — Dagger/Hilt, у iOS — Swinject, Needle, Dip. Контејнер може управљати опсегом (Scope): синглтон (једна инстанца по апликацији), опсег функције (по екрану) или нови објекат при сваком захтеву.

Dagger Hilt: DI за Android са генерисањем кода

Dagger Hilt — слој изнад Dagger од Google-а, стандардна DI библиотека за Android. Hilt поједностављује Dagger: уклања ручно креирање компонената, додаје @HiltAndroidApp, @AndroidEntryPoint и @Module. Hilt се интегрише са животним циклусом Android: ViewModel, Activity, Fragment, Service, BroadcastReceiver могу примати зависности кроз анотације. Генерисање кода се одвија у фази компилације — Dagger генерише имплементације компонената, што даје нулти runtime overhead.

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

// Module — дефинише како се креирају зависности
@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 прима зависност кроз конструктор
@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 омогућава DI
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels()
}

Dagger компоненте и опсези видљивости — @Singleton (за целу апликацију), @ActivityScoped (за Activity), @FragmentScoped (за Fragment), @ViewModelScoped (за ViewModel). Избор опсега одређује време живота објекта. @Singleton — једна инстанца по процесу, погодна за OkHttpClient и базу података. @ActivityScoped — објекат живи док живи Activity, за екранске зависности. @ViewModelScoped — новина Hilt 2.45+, објекат живи док живи ViewModel, што је згодно за корутинске опсеге.

Swinject: DI за iOS у Swift-у

Swinject — популарни DI оквир за iOS са отвореним кодом. Swinject пружа Container, Assemblies и различите опсеге. За разлику од Dagger, Swinject ради у runtime-у — зависности се решавају динамички без генерисања кода. То чини Swinject једноставнијим за подешавање, али отежава отклањање грешака: грешка нерешене зависности се појављује тек у runtime-у. Swinject подржава Constructor Injection, Property Injection и Method Injection.

swift
import Swinject

// Assembly — група регистрација
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 кроз 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 у AppDelegate или App
let assembler = Assembler([NetworkAssembly(), ServiceAssembly()])
let viewModel = assembler.resolver.resolve(ProfileViewModel.self)!

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

Swinject опсези — .transient (нови објекат сваки пут), .container (синглтон на контејнер), .graph (подразумевано — објекат се дели унутар једног графа зависности). За iOS апликације довољни су .container и .transient. Swinject такође подржава Assembler — груписање Assembly за модуларну архитектуру. За тестове Assembly се замењују са MockAssembly, што омогућава замену зависности без промене производног кода.

Поређење DI са Service Locator и ручним убризгавањем

DI vs Service Locator — оба обрасца решавају проблем управљања зависностима, али на различите начине. DI убризгава зависности у објекат, Service Locator пружа глобални регистар из ког објекат сам преузима зависности. DI јасно декларише зависности кроз конструктор (или сетер). Service Locator скрива зависности — one се преузимају унутар методе, што чини потпис мање информативним. DI се лакше тестира: довољно је ставити mock у конструктор. Service Locator захтева подешавање глобалног регистра за сваки тест.

КарактеристикаDependency InjectionService LocatorРучно убризгавање
Очигледност зависностиУ конструкторуСкривене у телу методеОчигледне
ТестирањеMock у конструкторуПодешавање Locator-аMock у конструктору
Сложеност подешавањаЗахтева DI контејнерГлобални регистарРучно креирање
Runtime overheadDagger — compile-timeRuntime lookupНема

DI vs ручно убризгавање — без DI контејнера, зависности се креирају ручно у фабрикама или AppDelegate. За 5-10 класа ручно убризгавање је једноставније — не захтева учење Dagger или Swinject. За 50+ класа ручно убризгавање постаје проблем: конструктори са 5-6 параметара, сложен редослед креирања, дуплирање кода. DI контејнер аутоматизује ове процесе и пружа јасан lifecycle. Ручно убризгавање без контејнера — добар избор за мале пројекте и прототипове.

Најбоље праксе Dependency Injection

Constructor Injection — стандард. Увек користите Constructor Injection за обавезне зависности. То чини зависности јасним и објекат увек спремним за рад. Setter Injection — само за опционе зависности (нпр. delegate или listener). Interface Injection — не користите, осим ако не пишете сопствену DI библиотеку. Constructor Injection — једини начин да се гарантује да се објекат креира у валидном стању.

Једна класа — једна одговорност. Ако конструктор класе захтева 5+ параметара, вероватно класа крши Single Responsibility Principle. Поделите класу на неколико са мање зависности. Знак: ако пишете класу ServiceManager са 6 различитих сервиса — то је анти-образац God Object. Издвојите пословну логику у Use Cases (Interactors), сваки са 1-2 зависности.

kotlin
// ❌ Лоше: 6 зависности — 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
)

// ✅ Добро: Use Cases са 1-2 зависности
class ProfileViewModel @Inject constructor(
    private val loadProfileUseCase: LoadProfileUseCase,
    private val trackAnalyticsUseCase: TrackAnalyticsUseCase
)

Опсег и lifecycle — бирајте одговарајући опсег за сваку зависност. Синглтони: OkHttpClient, база података, SharedPreferences. Опсег функције: репозиторијуми, Use Cases (ако немају стање). Transient: Value Objects, DateFormatter, парсери. Грешка опсега — чест проблем: синглтон који чува стање екрана доводи до цурења меморије. У Android Hilt @ActivityScoped решава овај проблем, у Swinject — .container са опрезом.

Често постављана питања

Зашто је потребан DI ако се може једноставно креирати са new?

new ствара чврсту везу између класа — не можете заменити имплементацију без промене кода. Тешко је тестирати: не можете убацити mock уместо правог сервиса. SRP се крши: класа одговара и за пословну логику и за креирање зависности. DI решава ове проблеме кроз убризгавање зависности споља и рад са апстракцијама.

Dagger Hilt или Koin — шта изабрати за Android?

Dagger Hilt — стандард од Google-а, compile-time DI са генерисањем кода, боље перформансе и интеграција са Jetpack. Koin — runtime DI, једноставнији за подешавање, али спорији и са грешкама у runtime-у. Бирејте Hilt за производне пројекте. Koin је погодан за прототипове и мале апликације.

Да ли је Swinject једини DI за iOS?

Не. За iOS су доступни: Swinject (runtime, популаран), Needle (compile-time од Uber-а), Dip (лагани), Weaver (заснован на Sourcery). Apple не пружа уграђени DI контејнер, али ручно убризгавање кроз init је стандардна пракса. За SwiftUI често је довољно ручно DI кроз Environment или @StateObject без спољних библиотека.

Може ли се DI користити без оквира?

Да. Ручно убризгавање кроз конструктор — то је DI без оквира. Service Locator — алтернатива без оквира. Фабрике и Factory Method — такође облик DI. Оквир (Dagger, Swinject) аутоматизује рутинску регистрацију и решавање зависности, али за 10-20 класа ручни DI је довољан.

Да ли је DI образац или принцип?

DI — техника (образац) која имплементира принцип Inversion of Control. За разлику од GoF образаца, DI нема строгу структуру од 3-4 класе. DI је начин организовања зависности, а не дизајн образац. DI контејнери (Dagger, Swinject) су оквири који аутоматизују ову технику.

Резиме

  • DI — техника убризгавања зависности споља кроз конструктор, сетер или метод
  • Dagger Hilt — стандард DI за Android са compile-time генерисањем кода и @HiltViewModel
  • Swinject — runtime DI за iOS са Container, Assembly и опсезима
  • Constructor Injection — пожељни начин за обавезне зависности
  • Опсег — Singleton за сервисе без стања, опсег функције за екранске зависности
  • Тестирање — DI поједностављује замену зависности на mock без промене кода

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође