Published on

Swift-da Combine framework bilan API-dan JSON yuklab olish

Authors

Avvalgi videoda URLSession.dataTask va @escaping closure bilan API-dan ma'lumot yuklab oldik. Bu videoda xuddi shu narsa Combine framework yordamida yanada qisqa va qulay tarzda amalga oshiriladi. Combine — iOS 13 da kelgan Apple framework-i.


Combine nima? — Posilka obunasi metaforasi

Combine-ni tushunish uchun oylik posilka obunasini tasavvur qiling:

Hayotiy holatCombine-da
Obunaga yozilishPublisher yaratish
Fabrika posilkani tayyorlaydiBackground thread-da ishlash
Posilka eshikka yetkaziladiMain thread-da qabul qilish
Qutini tekshirishtryMap — response tekshirish
Mahsulot to'g'ri ekanini tekshirishdecode — JSON → Model
Mahsulotni ishlatishsink — qiymatni olish va ishlatish
Obunani bekor qilish imkoniAnyCancellable

Model va ViewModel tuzilmasi

import Combine

struct PostModel: Identifiable, Codable {
    let userId: Int
    let id: Int
    let title: String
    let body: String
}

class DownloadWithCombineViewModel: ObservableObject {

    @Published var posts: [PostModel] = []

    // Publisher-ni saqlab qo'yish uchun — bekor qilish imkoni
    var cancellables = Set<AnyCancellable>()

    init() {
        getPosts()
    }

    func getPosts() { }
}

import CombineAnyCancellable ishlatish uchun shart.


Combine zanjiri — to'liq yozuv

func getPosts() {
    guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
        return
    }

    // 1. Publisher yaratish
    URLSession.shared.dataTaskPublisher(for: url)

        // 2. Background thread-da ishlash (aslida avtomatik, lekin aniqlik uchun)
        .subscribe(on: DispatchQueue.global(qos: .background))

        // 3. Main thread-da qabul qilish (UI yangilash uchun shart)
        .receive(on: DispatchQueue.main)

        // 4. Response tekshirish — quti shikastlanmagan ekanini tekshirish
        .tryMap(handleOutput)

        // 5. Decode — mahsulot to'g'ri ekanini tekshirish
        .decode(type: [PostModel].self, decoder: JSONDecoder())

        // 6. Sink — mahsulotni ishlatish
        .sink { completion in
            switch completion {
            case .finished:
                print("Muvaffaqiyatli yuklandi")
            case .failure(let error):
                print("Xato: \(error)")
            }
        } receiveValue: { [weak self] returnedPosts in
            self?.posts = returnedPosts
        }

        // 7. Store — "kvitansiya" saqlash (bekor qilish imkoni uchun)
        .store(in: &cancellables)
}

handleOutput — tryMap funksiyasi

tryMap ichidagi logikani alohida funksiyaga chiqarish — kod yanada toza bo'ladi:

func handleOutput(output: URLSession.DataTaskPublisher.Output) throws -> Data {
    guard
        let response = output.response as? HTTPURLResponse,
        response.statusCode >= 200 && response.statusCode < 300
    else {
        throw URLError(.badServerResponse)
    }

    return output.data
}

Asosiy zanjirda:

.tryMap(handleOutput)
// o'rniga qisqaroq:
// .tryMap { output in ... }

AnyCancellable va cancellables nima uchun kerak?

var cancellables = Set<AnyCancellable>()

// Zanjir oxirida:
.store(in: &cancellables)

Publisher — bir marta emas, vaqt o'tishi bilan qayta-qayta qiymat chiqarishi mumkin (masalan, har daqiqada yangi ma'lumot). Shuning uchun uni "kvitansiya" kabi saqlab qo'yish kerak — kerak bo'lsa bekor qilish uchun. cancellables — bu kvitansiyalar saqlanadigan "papka".


Xatolarni boshqarish — switch

.sink { completion in
    switch completion {
    case .finished:
        break  // muvaffaqiyatli — hech narsa kerak emas
    case .failure(let error):
        print("Xato: \(error)")
        // Haqiqiy ilovada: ekranda alert ko'rsatish
    }
} receiveValue: { [weak self] returnedPosts in
    self?.posts = returnedPosts
}

Xatolarni e'tiborsiz qoldirish — replaceError

Xato bo'lsa bo'sh massiv qaytarishni xohlasangiz:

URLSession.shared.dataTaskPublisher(for: url)
    .receive(on: DispatchQueue.main)
    .tryMap(handleOutput)
    .decode(type: [PostModel].self, decoder: JSONDecoder())
    .replaceError(with: [])  // xato bo'lsa bo'sh massiv
    .sink { [weak self] returnedPosts in
        self?.posts = returnedPosts
    }
    .store(in: &cancellables)

replaceError bilan completion bo'lmaydi — faqat receiveValue qoladi.


Escaping vs Combine taqqoslash

// ESCAPING (avvalgi video)
func downloadData(fromURL url: URL, completion: @escaping (_ data: Data?) -> ()) {
    URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data,
              error == nil,
              let response = response as? HTTPURLResponse,
              response.statusCode >= 200 && response.statusCode < 300
        else {
            completion(nil)
            return
        }
        completion(data)
    }.resume()
}

func getPosts() {
    guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return }
    downloadData(fromURL: url) { [weak self] returnedData in
        if let data = returnedData {
            guard let newPosts = try? JSONDecoder().decode([PostModel].self, from: data) else { return }
            DispatchQueue.main.async {
                self?.posts = newPosts
            }
        }
    }
}

// COMBINE (bu video)
func getPosts() {
    guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return }
    URLSession.shared.dataTaskPublisher(for: url)
        .receive(on: DispatchQueue.main)
        .tryMap(handleOutput)
        .decode(type: [PostModel].self, decoder: JSONDecoder())
        .sink { _ in } receiveValue: { [weak self] posts in self?.posts = posts }
        .store(in: &cancellables)
}

Xuddi shu natija — Combine bilan kod ancha qisqa va o'qilishi oson.


Muhim qoidalar

// ✅ HTTPS ishlatish shart — HTTP ishlamaydi
URL(string: "https://...")  // ✓
URL(string: "http://...")   // ✗ ATS (App Transport Security) xatosi

// ✅ .receive(on: DispatchQueue.main) — UI yangilash uchun shart
// Bo'lmasa: "Publishing changes from background threads is not allowed"

// ✅ [weak self] — strong reference cycle oldini olish
.sink { [weak self] posts in self?.posts = posts }

// ✅ .store(in: &cancellables) — publisher saqlanmasa ishlaydi, lekin boshqarish mumkin bo'lmaydi

Xulosa — Combine zanjiri

dataTaskPublisher → subscribeOn (background)receiveOn (main)
tryMap (response tekshirish)decode (JSONModel)
sink (qiymat olish)store (saqlash)
QadamFunksiyaMaqsad
1dataTaskPublisherPublisher yaratish
2subscribeOn(.background)Background thread
3receiveOn(.main)Main thread — UI uchun
4tryMapResponse tekshirish
5decodeJSON → Model
6sinkQiymat olish va ishlatish
7storePublisher saqlash

Combine iOS 13+ da ishlaydi. Eski versiyalarni qo'llab-quvvatlash kerak bo'lsa — avvalgi videodagi @escaping usuli tavsiya etiladi.

Buy mea coffee