Published on

Rasmlarni Async/Await, @escaping va Combine orqali yuklab olish

Authors

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:

  1. @escaping closure'lar orqali (eng eski usul)
  2. Combine framework orqali (reaktiv usul)
  3. Async/Await orqali (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?

  1. Kod ancha toza va o'qish uchun sodda bo'ldi. Uzundan-uzun closure'lar va "piramida" (nested) tuzilmalar yo'qoldi.
  2. Xatoliklarni ushlash qulaylashdi. Result tiplari yuzaki yechimlar emas, balki real throws orqali kuchli error handling ishlatildi.
  3. Thread'larni xavfsiz boshqarish (MainActor kabi vositalar) hisobiga dastur qotib qolishi yoki xato ishlashining oldi olindi.
Buy mea coffee