- Published on
Swift-da Combine framework bilan API-dan JSON yuklab olish
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
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 holat | Combine-da |
|---|---|
| Obunaga yozilish | Publisher yaratish |
| Fabrika posilkani tayyorlaydi | Background thread-da ishlash |
| Posilka eshikka yetkaziladi | Main thread-da qabul qilish |
| Qutini tekshirish | tryMap — response tekshirish |
| Mahsulot to'g'ri ekanini tekshirish | decode — JSON → Model |
| Mahsulotni ishlatish | sink — qiymatni olish va ishlatish |
| Obunani bekor qilish imkoni | AnyCancellable |
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 Combine — AnyCancellable 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 (JSON → Model)
→ sink (qiymat olish) → store (saqlash)
| Qadam | Funksiya | Maqsad |
|---|---|---|
| 1 | dataTaskPublisher | Publisher yaratish |
| 2 | subscribeOn(.background) | Background thread |
| 3 | receiveOn(.main) | Main thread — UI uchun |
| 4 | tryMap | Response tekshirish |
| 5 | decode | JSON → Model |
| 6 | sink | Qiymat olish va ishlatish |
| 7 | store | Publisher saqlash |
Combine iOS 13+ da ishlaydi. Eski versiyalarni qo'llab-quvvatlash kerak bo'lsa — avvalgi videodagi @escaping usuli tavsiya etiladi.