Published on

SwiftUI'da Dependency Injection nima va qanday ishlatiladi?

Authors

Singleton nima va uning muammolari qanday?

Agar siz internetdan ma'lumot yuklab oluvchi biror DataService yaratgan bo'lsangiz, uni katta ehtimol bilan Singleton yordamida ishlatgansiz:

class ProductionDataService {
    static let instance = ProductionDataService() // Singleton

    func getData() { ... }
}

// Boshqa faylda ishlatish:
ProductionDataService.instance.getData()

Lekin bu yondashuvning 3 ta katta muammosi bor:

  1. Global o'zgaruvchi: Uni ilovaning istalgan joyidan (kerak bo'lmasa ham) chaqirib olish mumkin. Xavfsiz emas.
  2. Init ni boshqarib bo'lmaydi: Biz qandaydir o'zgaruvchilarni qabul qiladigan init(URL: String) qila olmaymiz, chunki Singleton faqat bir marta o'zi yasaladi.
  3. Boshqasiga almashtirib bo'lmaydi: Tasavvur qiling, ilovangizni test qilmoqchisiz (Mock data kerak), lekin u faqatgina haqiqiy API'ga ulangan ProductionDataService orqali ishlamoqda.

Shu muammolarni hal qilish uchun Dependency Injection (DI) - ya'ni kerakli narsalarni tashqaridan (Init orqali) olib kirish usulidan foydalanamiz!

1-Qadam: Protokol yaratish

Mock (Test) va Production (Haqiqiy) xizmatlarni osongina almashtirish uchun, ikkalasini bitta qoidaga tushiramiz:

import SwiftUI
import Combine

protocol DataServiceProtocol {
    func getData() -> AnyPublisher<[PostModel], Error>
}

2-Qadam: Xizmatlarni yozish

Haqiqiy Internetga ulanadigan xizmat:

class ProductionDataService: DataServiceProtocol {
    let url: URL

    // URL ni tashqaridan qabul qilib olamiz (Bu ham DI)
    init(url: URL) {
        self.url = url
    }

    func getData() -> AnyPublisher<[PostModel], Error> {
        // ... Internetdan ma'lumot tortish (URLSession va Combine)
    }
}

Faqat test uchun mo'ljallangan "Yolg'on" (Mock) xizmat:

class MockDataService: DataServiceProtocol {
    let testData: [PostModel] = [
        PostModel(id: 1, title: "Test Post 1"),
        PostModel(id: 2, title: "Test Post 2")
    ]

    func getData() -> AnyPublisher<[PostModel], Error> {
        // Just yordamida shunchaki tayyor arrayni qaytarib yuboramiz
        Just(testData)
            .tryMap({ $0 })
            .eraseToAnyPublisher()
    }
}

3-Qadam: View Modelga "Inyektsiya" qilish

Endi ViewModel imiz to'g'ridan-to'g'ri ProductionDataService.instance ni emas, balki protokoldan kelgan ixtiyoriy xizmatni init orqali o'ziga qabul qiladi:

class DependencyInjectionViewModel: ObservableObject {
    @Published var dataArray: [PostModel] = []
    var cancellables = Set<AnyCancellable>()

    // Qaysi xizmat ekanligi muhim emas, asosiysi Protokolga tushsa bo'ldi
    let dataService: DataServiceProtocol

    // INJECTION QILAMIZ:
    init(dataService: DataServiceProtocol) {
        self.dataService = dataService
        loadPosts()
    }

    private func loadPosts() {
        dataService.getData()
            .sink(receiveCompletion: { _ in }, receiveValue: { [weak self] returnedPosts in
                self?.dataArray = returnedPosts
            })
            .store(in: &cancellables)
    }
}

Natija va Ishlatish

Eng ajoyib joyi shu yerdaki, endi SwiftUI sahifasini o'zida turib xohlagan xizmatimizni osongina berib yuborishimiz mumkin:

struct DependencyInjectionBootcamp: View {
    @StateObject private var vm: DependencyInjectionViewModel

    init(dataService: DataServiceProtocol) {
        // View Modelga xizmatni Inyektsiya qilyapmiz!
        _vm = StateObject(wrappedValue: DependencyInjectionViewModel(dataService: dataService))
    }

    var body: some View {
        ScrollView {
            VStack {
                ForEach(vm.dataArray) { post in
                    Text(post.title)
                }
            }
        }
    }
}

Qayerdadir bu oyna chaqirilganda:

// Ishlayotgan Asl Ilovada:
let prodService = ProductionDataService(url: URL(string: "https://mysite.com/posts")!)
DependencyInjectionBootcamp(dataService: prodService)

// Yoki Preview'da / Test qilayotganda:
let mockService = MockDataService()
DependencyInjectionBootcamp(dataService: mockService)

Xulosa: DI eshitilishiga juda qiyin atama bo'lsa-da, u shunchaki zarur bo'lgan klass/xizmatni ichkaridan yaratmasdan, tashqaridan (init orqali) berib yuborishdir. Bu ilovani qulay, test qilish oson va bo'laklarga ajralgan holatda saqlashga juda katta yordam beradi.

Buy mea coffee