Published on

Swift-da URLSession va Escaping Closure bilan API-dan JSON yuklab olish

Authors

Oldingi videolarda background thread, escaping closure va Codable protokoli o'rganildi. Bu videoda ularning barchasi birlashadi — haqiqiy API-dan ma'lumot yuklab, ilovada ekranda ko'rsatiladi.

Test uchun bepul ochiq API ishlatiladi: jsonplaceholder.typicode.com — bu haqiqiy backend bo'lmasdan ham JSON ma'lumot qaytaruvchi xizmat.


Model

API-dan kelayotgan JSON formatiga qarab model yaratiladi:

// API dan keladigan JSON:
// { "userId": 1, "id": 1, "title": "...", "body": "..." }

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

IdentifiableForEach-da ishlatish uchun. Codable — JSON-dan avtomatik decode qilish uchun. Xususiyat nomlari JSON kalit nomlari bilan aynan mos bo'lishi shart — aks holda decode ishlaydi, lekin qiymatlar nil bo'ladi.


ViewModel — asosiy tuzilma

class DownloadWithEscapingViewModel: ObservableObject {

    @Published var posts: [PostModel] = []

    init() {
        getPosts()
    }

    func getPosts() { }
}

URLSession bilan ma'lumot yuklash

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

    URLSession.shared.dataTask(with: url) { data, response, error in

        // 1. Data borligini tekshirish
        guard let data = data else {
            print("Ma'lumot yo'q")
            return
        }

        // 2. Xato yo'qligini tekshirish
        guard error == nil else {
            print("Xato: \(error!)")
            return
        }

        // 3. Response HTTP URL Response ekanini tekshirish
        guard let response = response as? HTTPURLResponse else {
            print("Noto'g'ri response")
            return
        }

        // 4. Status code 200-299 orasida ekanini tekshirish
        guard response.statusCode >= 200 && response.statusCode < 300 else {
            print("Status code noto'g'ri: \(response.statusCode)")
            return
        }

        // Barcha tekshiruvlar o'tdi — ma'lumot tayyor
        print("Muvaffaqiyatli yuklandi")

    }.resume()  // ← .resume() bo'lmasa task BOSHLANMAYDI
}

.resume() nima?

dataTask task-ni tayyorlaydi, lekin ishga tushirmaydi. .resume() — ishga tushirish buyrug'i. Nomi "resume" — chunki pause/suspend qilingan task-ni ham qayta ishga tushirish uchun ishlatiladi.

HTTP status code-lar

200299Muvaffaqiyatli
400499Client xatosi (noto'g'ri so'rov)
500599Server xatosi

Keng tarqalgan qisqartma — bitta guard

Yuqoridagi to'rtta tekshiruvni bitta guard-ga birlashtirish mumkin:

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 {
        print("Ma'lumot yuklanmadi")
        return
    }

    // Shu yerda data tayyor

}.resume()

Decode va UI yangilash

URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
    guard let data = data, error == nil else { return }

    // Codable bilan decode
    guard let newPost = try? JSONDecoder().decode(PostModel.self, from: data) else {
        return
    }

    // UI yangilash — MAIN THREAD-DA bo'lishi shart
    DispatchQueue.main.async {
        self?.posts.append(newPost)
    }

}.resume()

Nima uchun DispatchQueue.main kerak?

URLSession.dataTask avtomatik ravishda background thread-ga o'tadi. Ma'lumot yuklanib bo'lganda ham background thread-da turiladi. @Published o'zgaruvchini background thread-dan yangilash — Xcode xatosi beradi:

Publishing changes from background threads is not allowed

Shuning uchun DispatchQueue.main.async { } ichida yangilash — bu asosiy qoidalardan biri.

weak self nima uchun?

URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
    DispatchQueue.main.async {
        self?.posts.append(newPost)  // weak bo'lgani uchun optional
    }
}

[weak self] — strong reference cycle (retain cycle) oldini olish uchun. Agar weak bo'lmasa, closure ViewModel-ni "ushlab" turadi — xotiradan chiqarilmaydi. weak bilan — ViewModel yo'q bo'lsa, self nil bo'ladi va xotira muammosi bo'lmaydi.


Umumiy downloadData funksiyasi

Bir nechta turli URL-lardan ma'lumot yuklash kerak bo'lganda, har safar yuqoridagi kodni qayta yozish keraksiz. Umumiy funksiya:

func downloadData(fromURL url: URL, completion: @escaping (_ returnedData: 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)  // muvaffaqiyatsiz — nil qaytarish
            return
        }

        completion(data)  // muvaffaqiyatli — data qaytarish

    }.resume()
}

@escaping — async funksiya bo'lgani uchun: downloadData tugashidan keyin completion chaqiriladi (escaping closure mavzusida tushuntirilgan).

Foydalanish

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

    downloadData(fromURL: url) { [weak self] returnedData in
        if let data = returnedData {
            guard let newPost = try? JSONDecoder().decode(PostModel.self, from: data) else {
                return
            }
            DispatchQueue.main.async {
                self?.posts.append(newPost)
            }
        }
    }
}

Massiv postlarni yuklash

Bitta post o'rniga barcha postlarni yuklash:

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 {
            // [PostModel] — massiv, PostModel emas
            guard let newPosts = try? JSONDecoder().decode([PostModel].self, from: data) else {
                return
            }
            DispatchQueue.main.async {
                self?.posts = newPosts  // append emas, to'liq almashtirish
            }
        }
    }
}

View

struct DownloadWithEscapingBootcamp: View {

    @StateObject var vm = DownloadWithEscapingViewModel()

    var body: some View {
        List {
            ForEach(vm.posts) { post in
                VStack(alignment: .leading) {
                    Text(post.title)
                        .font(.headline)
                    Text(post.body)
                        .foregroundColor(.gray)
                }
                .frame(maxWidth: .infinity, alignment: .leading)
            }
        }
    }
}

app.quicktype.io — JSON dan Model yaratish vositasi

Murakkab JSON-da model yaratish qiyin bo'lishi mumkin — xususiyatlar ko'p, turlar har xil. app.quicktype.io vositasida JSON ma'lumotni joylashtirish, til sifatida Swift-ni tanlash — va tayyor Codable struct avtomatik yaratiladi. Bu boshlang'ich va tajribali dasturchilarga ham vaqt tejaydi.


Xulosa

URLURLSession.dataTaskguard (data, error, response, statusCode)
JSONDecoder().decodeDispatchQueue.main.async → @Published o'zgaruvchi
TushunchaMa'nosi
.resume()Task-ni ishga tushirish — unutilsa task boshlanmaydi
Status code 200–299Muvaffaqiyatli response
DispatchQueue.main.asyncUI yangilash asosiy thread-da bo'lishi shart
[weak self]Strong reference cycle-dan himoya
@escapingAsync closure — funksiya tugagandan keyin chaqiriladi

Keyingi videoda xuddi shu narsa Combine framework yordamida yanada qulay tarzda amalga oshiriladi.

Buy mea coffee