Published on

SwiftUI crypto app — ViewModel va data service yordamida tangalarni yuklash

Authors

SwiftUI crypto app — ViewModel va data service yordamida tangalarni yuklash

Ushbu darsda biz nihoyat API orqali tarmoqdan real ma'lumotlarni yuklab olishni amalga oshiramiz. MVVM arxitekturasini kengaytirib, tarmoq so'rovlari va ma'lumotlarni yuklash vazifasini bajaruvchi CoinDataService nomli yangi servis (Service) qatlamini yaratamiz.

Ushbu servis Combine-ning Publisher (tarqatuvchi) imkoniyatlaridan foydalanadi. HomeViewModel esa ushbu servisga Subscriber (obunachi) bo'ladi va ma'lumot kelishi bilan uni qabul qilib ekranga uzatadi.


1. CoinDataService klassini yaratish

services papkasi ichida CoinDataService.swift nomli yangi fayl yaratamiz. Bu klass faqat ma'lumotlarni tarmoqdan yuklash, tekshirish va modelga o'tkazish (decoding) bilan shug'ullanadi:

import Foundation
import Combine

class CoinDataService {

    // Tarmoqdan kelgan barcha tangalar ro'yxatini nashr qiluvchi (Publisher) o'zgaruvchi
    @Published var allCoins: [CoinModel] = []

    // Obunani (Subscription) saqlash va keyinchalik bekor qilish uchun o'zgaruvchi
    private var coinSubscription: AnyCancellable?

    init() {
        getCoins()
    }

    private func getCoins() {
        // API URL manzilini tekshiramiz
        guard let url = URL(string: "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250&page=1&sparkline=true&price_change_percentage=24h") else { return }

        // URLSession yordamida Combine data task publisher yaratamiz
        coinSubscription = URLSession.shared.dataTaskPublisher(for: url)
            // So'rovni fondagi oqimda (Background Thread) bajarilishini ta'minlaymiz
            .subscribe(on: DispatchQueue.global(qos: .default))
            // Server javobini (HTTPResponse) va status kodini tekshiramiz
            .tryMap { (output) -> Data in
                guard let response = output.response as? HTTPURLResponse,
                      response.statusCode >= 200 && response.statusCode < 300 else {
                    throw URLError(.badServerResponse)
                }
                return output.data
            }
            // Ma'lumotlarni interfeysga uzatishdan oldin Asosiy oqimga (Main Thread) qaytamiz
            .receive(on: DispatchQueue.main)
            // JSON ma'lumotlarni [CoinModel] modeliga decode qilamiz
            .decode(type: [CoinModel].self, decoder: JSONDecoder())
            // Ma'lumotlarni qabul qilib olamiz
            .sink(receiveCompletion: { (completion) in
                switch completion {
                case .finished:
                    break
                case .failure(let error):
                    print("Ma'lumot yuklashda xatolik yuz berdi: \(error.localizedDescription)")
                }
            }, receiveValue: { [weak self] (returnedCoins) in
                // Qabul qilingan tangalarni local array-ga joylaymiz
                self?.allCoins = returnedCoins
                // Bizga bu so'rov faqat bir marta kerak bo'lganligi uchun obunani bekor qilamiz
                self?.coinSubscription?.cancel()
            })
    }
}

2. HomeViewModel-ni CoinDataService-ga obuna qilish

Endi home/viewmodels/HomeViewModel.swift fayliga o'tamiz. Ushbu ViewModel-da CoinDataService obyektini yaratamiz va undagi @Published var allCoins o'zgaruvchisiga obuna bo'lamiz:

import Foundation
import Combine

class HomeViewModel: ObservableObject {

    @Published var allCoins: [CoinModel] = []
    @Published var portfolioCoins: [CoinModel] = []

    // Ma'lumotlar servisi obyekti
    private let dataService = CoinDataService()

    // ViewModel-dagi barcha obunalarni saqlash uchun to'plam
    private var cancelables = Set<AnyCancellable>()

    init() {
        addSubscribers()
    }

    func addSubscribers() {
        // Servisdagi allCoins publisher-ga obuna bo'lamiz
        dataService.$allCoins
            .sink { [weak self] (returnedCoins) in
                // Servis ma'lumot olganida, biz ham o'z ro'yxatimizni yangilaymiz
                self?.allCoins = returnedCoins
            }
            // Obunani to'plam ichida saqlab qolamiz
            .store(in: &cancelables)
    }
}

3. Kombayn (Combine) qanday ishlaydi

Combine zanjiri (pipeline) quyidagi tartibda ishlaydi:

  1. dataTaskPublisher: Ma'lumotni yuklashni boshlaydi (publisher).
  2. subscribe(on:): Bu operatsiyani fondagi oqimga (background thread) o'tkazadi.
  3. tryMap: Yuklangan ma'lumot to'g'ri kelganini tekshiradi (200-299 status kodlar).
  4. receive(on:): Qabul qilish jarayonini asosiy oqimga (main thread) qaytaradi.
  5. decode: Kelgan JSON formatdagi ma'lumotlarni biz yaratgan modelga moslab decode qiladi.
  6. sink: Xatolik yoki muvaffaqiyatli yakunlanishni kuzatadi hamda yakuniy natijani bizga yetkazadi.

Ushbu amallardan so'ng HomeView Canvas preview-sini qayta ishga tushirsangiz, internet orqali CoinGecko-dan 250 ta eng mashhur kriptovalyuta ma'lumotlari yuklanib ekranga chiqarilganini ko'rishingiz mumkin.

Keyingi darsda biz tarmoq so'rovini yanada toza va qayta ishlatiladigan (reusable networking layer) holatga keltiramiz! 🚀

Buy mea coffee