- Published on
SwiftUI'da Dependency Injection nima va qanday ishlatiladi?
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
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:
- Global o'zgaruvchi: Uni ilovaning istalgan joyidan (kerak bo'lmasa ham) chaqirib olish mumkin. Xavfsiz emas.
- Init ni boshqarib bo'lmaydi: Biz qandaydir o'zgaruvchilarni qabul qiladigan
init(URL: String)qila olmaymiz, chunki Singleton faqat bir marta o'zi yasaladi. - Boshqasiga almashtirib bo'lmaydi: Tasavvur qiling, ilovangizni test qilmoqchisiz (Mock data kerak), lekin u faqatgina haqiqiy API'ga ulangan
ProductionDataServiceorqali 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.