- Published on
Rasmlarni Async/Await, @escaping va Combine orqali yuklab olish
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
Rasmlarni yuklab olish: Eskidan Yangigacha
Ushbu darsda biz nihoyat Async/Await kalit so'zlaridan amalda foydalanishni boshlaymiz. Buni yaxshiroq tushunish uchun bitta vazifani — internetdan rasmni yuklab olishni uch xil usulda bajaramiz:
@escapingclosure'lar orqali (eng eski usul)Combineframework orqali (reaktiv usul)Async/Awaitorqali (eng zamonaviy usul)
Eski usullarni yangisi bilan solishtirish orqali, qaysi kod bo'laklari o'zgargani va nima uchun yangi usul afzalroq ekanligi ongimizda ancha aniqroq shakllanadi.
Umumiy sozlamalar (Barcha usullar uchun)
Keling, avvalo har uchala usulda ham serverdan qaytadigan javobni tekshirib, uni rasmga aylantirib beradigan yordamchi funksiya yozib olamiz. Bu bizga kodni takrorlamaslik imkonini beradi.
class DownloadImageAsyncImageLoader {
let url = URL(string: "https://picsum.photos/200")!
// Serverdan kelgan Data va Response ni qabul qilib UIImage qaytaruvchi funksiya
func handleResponse(data: Data?, response: URLResponse?) -> UIImage? {
guard
let data = data,
let image = UIImage(data: data),
let response = response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 300 else {
return nil
}
return image
}
}
1-usul: @escaping Closures orqali yuklash
Escaping closure'lar — bu Swift'da asinxron operatsiyalarni bajarishning azaliy usulidir. Biz ma'lumotni so'raymiz va javob qaytganida u qachondir (kelajakda) ijro etilishi uchun completionHandler yuboramiz.
// Loader klassidagi funksiya
func downloadWithEscaping(completionHandler: @escaping (_ image: UIImage?, _ error: Error?) -> Void) {
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
let image = self?.handleResponse(data: data, response: response)
completionHandler(image, error)
}
.resume()
}
Va biz bu funksiyani o'zimizning ViewModel'imizda chaqirganimizda, UI ni yangilash uchun orqa fonga tushib qolgan oqimni (thread'ni) yana Asosiy oqimga (Main Thread) qaytarishimiz shart bo'ladi:
func fetchImageWithEscaping() {
loader.downloadWithEscaping { [weak self] image, error in
// UI o'zgarishlari har doim Asosiy Thread'da bo'lishi kerak!
DispatchQueue.main.async {
self?.image = image
}
}
}
2-usul: Combine framework orqali yuklash
Combine framework biroz zamonaviyroq va "data stream" (ma'lumotlar oqimi) ko'rinishida ishlaydi.
import Combine
// Loader klassidagi funksiya
func downloadWithCombine() -> AnyPublisher<UIImage?, Error> {
URLSession.shared.dataTaskPublisher(for: url)
.map { handleResponse(data: $0.data, response: $0.response) }
.mapError { $0 as Error }
.eraseToAnyPublisher()
}
Bu funksiyani chaqirganda esa, Combine'ning maxsus qulayliklaridan foydalanib oqimni oson o'zgartira olamiz va yoziluvchilarni (subscribers) obuna qilamiz (sink orqali).
func fetchImageWithCombine() {
loader.downloadWithCombine()
.receive(on: DispatchQueue.main) // Asosiy thread'ga o'tish
.sink { completion in
// Xatoliklarni boshqarish
} receiveValue: { [weak self] image in
self?.image = image
}
.store(in: &cancellables) // obunani xotirada saqlash
}
3-usul: Async / Await orqali yuklash (Zamonaviy yondashuv)
Nihoyat, biz kiritmoqchi bo'lgan eng yaxshi va eng xavfsiz usul. E'tibor bering, endi bizga qanaqadir closure'lar yoki mantiqiy chalkashliklar kerak emas! Biz shunchaki kodni o'qiymiz, go'yoki u asinxron emasdek.
// Loader klassidagi funksiya
func downloadWithAsync() async throws -> UIImage? {
// try await yordamida ma'lumotni kelishini kutamiz
let (data, response) = try await URLSession.shared.data(from: url)
// Ma'lumot kelgach kod davom etadi
let image = handleResponse(data: data, response: response)
return image
}
Bu funksiyada async so'zi "ushbu funksiya qachondir o'z ishi vaqtida to'xtab turishi va kutishi mumkin" degan ma'noni beradi. await so'zi esa aynan o'sha to'xtalish (suspension point) nuqtasidir.
Uni qanday chaqiramiz? (Task va MainActor)
Agar funksiya async bo'lsa, uni faqat boshqa async muhitidangina chaqira olamiz. Oddiy sinxron ViewModel muhitidan turib asinxron muhitga kirish uchun biz Task (vazifa) ochishimiz kerak. Shuningdek, UI'ni yangilashda DispatchQueue.main.async o'rniga endi maxsus MainActor'dan foydalanamiz.
func fetchImageWithAsync() {
// Asinxron muhit yaratish uchun Task ochamiz
Task {
do {
// Rasm yuklanishini kutib turamiz
let image = try await loader.downloadWithAsync()
// UI'ni yangilash uchun Asosiy Thread'ga (MainActor'ga) o'tamiz
await MainActor.run {
self.image = image
}
} catch {
print(error.localizedDescription)
}
}
}
Xulosa
Async / Await bizga nima berdi?
- Kod ancha toza va o'qish uchun sodda bo'ldi. Uzundan-uzun closure'lar va "piramida" (nested) tuzilmalar yo'qoldi.
- Xatoliklarni ushlash qulaylashdi.
Resulttiplari yuzaki yechimlar emas, balki realthrowsorqali kuchli error handling ishlatildi. - Thread'larni xavfsiz boshqarish (MainActor kabi vositalar) hisobiga dastur qotib qolishi yoki xato ishlashining oldi olindi.