15. Dependency Injection va DI Container: Inversion of Control
🏛️ iOS Design Patterns — Mukammal Enterprise & Architecture Qo‘llanma
Dependency Injection (DI) va Zamonaviy DI Container'lar
Dasturlashda eng ko‘p uchraydigan va kodni mo‘rt (fragile), testlab bo‘lmaydigan qiluvchi xato — bu obyektlarning o‘z ichida qaramliklarni (dependencies) init qilishi (tight coupling). Dependency Injection (DI) — bu arxitekturaning fundamental tamoyili bo‘lib, Inversion of Control (IoC) va SOLID'ning Dependency Inversion Principle (DIP) qoidasini amalda ta’minlaydi.
Ushbu darsda biz DI shakllari, Service Locator anti-patterni xavflari, Swift 6 da Sendable va actor-safe bo‘lgan yengil DI Container hamda @Injected Property Wrapper arxitekturasini quramiz.
1. Tight Coupling vs Inversion of Control (IoC)
Obyekt o‘ziga kerakli xizmatni o‘zi yaratsa:
// ❌ ANTI-PATTERN: Tight Coupling
final class ProfileViewModel {
private let apiService = NetworkService() // Qattiq bog'langan!
private let analytics = FirebaseAnalytics()
// Testda NetworkService o'rniga Mock berib bo'lmaydi!
}
Buning o‘rniga, obyekt o‘z qaramliklarini tashqaridan qabul qilishi shart:
graph TD
subgraph "Tight Coupling (O'tmish)"
A[ProfileViewModel] -->|To'g'ridan-to'g'ri yaratadi| B[NetworkService]
A -->|To'g'ridan-to'g'ri yaratadi| C[FirebaseAnalytics]
end
subgraph "Inversion of Control (DI)"
VM[ProfileViewModel] -->|Faqat protokollarga bog'liq| P1[NetworkServiceProtocol]
VM -->|Faqat protokollarga bog'liq| P2[AnalyticsServiceProtocol]
DI[DI Container / Assembler] -.->|Injects Real Impl| P1
DI -.->|Injects Mock Impl| P2
end
2. Dependency Injection Shakllari
iOS'da DI ning 3 xil usuli mavjud:
| Usul | Afzalligi | Kamchiligi | Tavsiya |
|---|---|---|---|
| Constructor (Initializer) Injection | 100% Compile-time xavfsiz, immutability (let), qaramliksiz obyekt yaratilmaydi | Init parametrlar ko‘payib ketishi mumkin | ⭐ Oltin standart (Default) |
| Property Injection | Storyboard / UIKit lifecycle yoki ixtiyoriy pluginlar uchun qulay | Mutable (var) bo‘lishi shart, nil xavfi bor | Faqat shart bo‘lganda |
| Method Injection | Bir martalik parametr (masalan, formatter yoki logger) uzatish | Doimiy servislar uchun noqulay | Yordamchi amallarga |
Constructor Injection Namunasi (Swift 6)
protocol NetworkServiceProtocol: Sendable {
func fetchUserProfile(id: String) async throws -> UserProfile
}
protocol AnalyticsServiceProtocol: Sendable {
func track(event: String)
}
@MainActor
final class ProfileViewModel {
// Immutability: tashqaridan beriladi va qayta o'zgarmaydi
private let networkService: any NetworkServiceProtocol
private let analytics: any AnalyticsServiceProtocol
// Constructor Injection
init(
networkService: any NetworkServiceProtocol,
analytics: any AnalyticsServiceProtocol
) {
self.networkService = networkService
self.analytics = analytics
}
func loadUser(id: String) async {
do {
let profile = try await networkService.fetchUserProfile(id: id)
analytics.track(event: "profile_loaded_success")
// Update UI state
} catch {
analytics.track(event: "profile_load_failed")
}
}
}
3. Service Locator — Nega bu Anti-Pattern?
Ko‘plab jamoalar DI o‘rniga static ServiceLocator yaratishadi:
// ⚠️ XAVFLI: Service Locator Anti-Pattern
final class ServiceLocator {
static let shared = ServiceLocator()
private var services: [String: Any] = [:]
func register<T>(_ service: T) {
services[String(describing: T.self)] = service
}
func resolve<T>() -> T {
guard let service = services[String(describing: T.self)] as? T else {
fatalError("Servis topilmadi! Runtime Crash!")
}
return service
}
}
Nega Service Locator xavfli?
- Yashirin Qaramliklar (Hidden Dependencies): Sinf tashqaridan qaraganda hech qanday parametr so‘ramaydi (
init()). Ammo ichidaServiceLocator.shared.resolve()chaqiriladi. Kodni o‘qigan dasturchi bu sinf ishlashi uchun qaysi servislar registratsiya qilingan bo‘lishi kerakligini bilmaydi. - Runtime Crash: Agar bitta servis unutilgan bo‘lsa, kod compile bo‘ladi, lekin dastur ishga tushganda
fatalErrorberadi. - Temporal Coupling: Obyektlar yaratilish tartibiga qattiq bog‘lanib qoladi.
4. Swift 6 Xavfsiz, Yengil DI Container va @Injected Property Wrapper
Keling, zamonaviy SwiftUI va Swift Concurrency talablariga to‘liq javob beradigan, Sendable va type-safe Lightweight DI Container quramiz.
sequenceDiagram
autonumber
participant App as App Launch / Assembler
participant Container as AppContainer (Actor)
participant VM as HomeViewModel
participant Network as LiveNetworkService
App->>Container: register(LiveNetworkService.self)
Note over Container: Servislar Type bo'yicha saqlanadi
VM->>Container: resolve(NetworkServiceProtocol.self)
Container-->>VM: return LiveNetworkService instance
VM->>Network: fetchData()
Swift 6 Real Implementatsiyasi:
import Foundation
// 1. Thread-safe DI Container
public final class DIContainer: @unchecked Sendable {
public static let shared = DIContainer()
private let lock = NSLock()
private var factories: [String: () -> Any] = [:]
private var singletons: [String: Any] = [:]
public init() {}
// Yangi factory registratsiyasi (Har safar yangi instance)
public func register<T>(
_ type: T.Type,
factory: @escaping () -> T
) {
lock.lock()
defer { lock.unlock() }
let key = String(reflecting: type)
factories[key] = factory
}
// Singleton registratsiyasi (Bitta doimiy xotiradagi instance)
public func registerSingleton<T>(
_ type: T.Type,
instance: T
) {
lock.lock()
defer { lock.unlock() }
let key = String(reflecting: type)
singletons[key] = instance
}
// Servisni qabul qilish
public func resolve<T>(_ type: T.Type = T.self) -> T {
lock.lock()
defer { lock.unlock() }
let key = String(reflecting: type)
// 1. Singleton tekshiruvi
if let instance = singletons[key] as? T {
return instance
}
// 2. Factory tekshiruvi
if let factory = factories[key], let instance = factory() as? T {
return instance
}
fatalError("DIContainer: \(key) turi uchun servis registratsiya qilinmagan!")
}
}
// 2. Ergonomik @Injected Property Wrapper
@propertyWrapper
public struct Injected<Service> {
private var service: Service
public init(container: DIContainer = .shared) {
self.service = container.resolve(Service.self)
}
public var wrappedValue: Service {
get { service }
mutating set { service = newValue }
}
}
5. SwiftUI va XCTest'da Foydalanish
App Bootstrap:
func configureDependencies() {
let container = DIContainer.shared
container.registerSingleton(NetworkServiceProtocol.self, instance: LiveNetworkService())
container.registerSingleton(AnalyticsServiceProtocol.self, instance: MixpanelAnalyticsService())
}
SwiftUI ViewModel'da Ishlatish:
import SwiftUI
@Observable
@MainActor
final class HomeViewModel {
@Injected private var network: NetworkServiceProtocol
@Injected private var analytics: AnalyticsServiceProtocol
var items: [String] = []
func load() async {
analytics.track(event: "home_screen_open")
// Network orqali yuklash...
}
}
XCTest'da 100% Izolatsiyalangan Unit Test:
import XCTest
final class MockNetworkService: NetworkServiceProtocol {
var stubbedProfile: UserProfile?
var didCallFetch = false
func fetchUserProfile(id: String) async throws -> UserProfile {
didCallFetch = true
if let profile = stubbedProfile {
return profile
}
throw URLError(.badServerResponse)
}
}
final class ProfileViewModelTests: XCTestCase {
func test_loadUser_success() async {
// DI Container'siz ham Constructor orqali to'g'ridan-to'g'ri mock berish eng toza yo'l:
let mockNetwork = MockNetworkService()
mockNetwork.stubbedProfile = UserProfile(id: "123", name: "Azimjon")
let mockAnalytics = MockAnalyticsService()
let sut = ProfileViewModel(networkService: mockNetwork, analytics: mockAnalytics)
await sut.loadUser(id: "123")
XCTAssertTrue(mockNetwork.didCallFetch)
XCTAssertEqual(mockAnalytics.events, ["profile_loaded_success"])
}
}
6. Senior & Staff Xulosa
- Over-engineering qilmang: Agar ilovangiz o‘rtacha hajmda bo‘lsa, Constructor Injection + oddiy Factory / Coordinator barcha ehtiyojingizni qoplaydi. 10 xil uchinchi tomon DI kutubxonalarini (Swinject, Needle, Factory) yuklab loyihani og‘irlashtirish shart emas.
- SwiftUI Environment: SwiftUI ning
@Environmentva@EnvironmentObjectlari View darajasidagi Dependency Injection hisoblanadi. Ammo biznes-mantiq (ViewModel, Domain Interactor) darajasida Sof Swift DI usullari qo‘llanilishi maqsadga muvofiq. - Property Wrapper ehtiyoti:
@Injectedwrapper'laridan faqat arxitektura ruxsat bergan cheklangan qatlamlarda (masalan, View yoki Coordinator) foydalaning, toza biznes-mantiq sinflarida esa doimo Constructor Injectionni tanlang!
Bilimni Mustahkamlash uchun Savollar
<details> <summary><b>1. Nega Constructor Injection Property Injection'dan ustun hisoblanadi?</b></summary> Constructor Injection ob’yektning barcha zaruriy qaramliklarini yaratilish paytidayoq (init) talab qiladi. Bu let (immutability) imkonini beradi va ob’yektning chala yoki yaroqsiz (nil) holatda yaratilishini compile vaqtidayoq taqiqlaydi. </details>
<details> <summary><b>2. Service Locator va Dependency Injection o'rtasidagi asosiy farq nima?</b></summary> DI da ob’yekt o'z qaramliklaridan bexabar bo‘ladi — ular unga tashqaridan beriladi (passiv qabul qiluvchi). Service Locator'da esa ob’yekt global reyestrga murojaat qilib, o‘zi qidirib topadi (faol qidiruvchi). Bu esa qaramliklarni yashiradi va testlashni murakkablashtiradi. </details>