- Published on
SwiftUI crypto app — yakuniy optimizatsiyalar
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
SwiftUI crypto app — yakuniy optimizatsiyalar
Ilova deyarli tayyor. Bu darsda kichik lekin muhim optimizatsiyalar qilamiz:
- iPad-da to'g'ri ko'rinishi
- Network so'rovlarini background threadda decode qilish
retry()bilan muvaffaqiyatsiz so'rovlarni qayta urinish- Bo'sh portfel uchun foydalanuvchiga xabar
1. iPad qo'llab-quvvatlash
iPad simulatorida ilovani ishga tushiring. Muammo: NavigationView iPad-da ikkita panelda ko'rinadi (split view) — chap tomonida asosiy ekran, o'ng tomonida detail ekran. Bu iphone-dan farqli.
SwiftUICryptoApp.swift-da bir qator qo'shish yetarli:
NavigationView {
HomeView()
}
.navigationViewStyle(StackNavigationViewStyle())
// ↑ Bu iPad-ni ham iPhone kabi "stack" navigatsiyasiga majbur qiladi
Endi iPad ham iPhone kabi ishlaydi. LazyVGrid va GeometryReader avtomatik o'lchamga moslashadi — qo'shimcha hech narsa kerak emas.
2. NetworkingManager optimizatsiyasi
Joriy holat:
// NetworkingManager.swift
static func download(url: URL) -> AnyPublisher<Data, Error> {
return URLSession.shared.dataTaskPublisher(for: url)
.subscribe(on: DispatchQueue.global(qos: .default)) // ← keraksiz!
.tryMap({ try handleURLResponse(output: $0, url: url) })
.receive(on: DispatchQueue.main) // ← bu ham to'g'ri joyda emas
.eraseToAnyPublisher()
}
Muammo: URLSession.dataTaskPublisher allaqachon background threadda ishlaydi. subscribe(on:) kerak emas. Va receive(on: DispatchQueue.main) decode-dan oldin kelsa — decode main threadda bo'ladi.
Optimizatsiyalangan versiya:
static func download(url: URL) -> AnyPublisher<Data, Error> {
return URLSession.shared.dataTaskPublisher(for: url)
// subscribe(on:) olib tashlandi — allaqachon background-da
.tryMap({ try handleURLResponse(output: $0, url: url) })
.retry(3) // Muvaffaqiyatsiz bo'lsa — 3 marta qayta urinish
.eraseToAnyPublisher()
// receive(on: main) service fayllariga ko'chirildi
}
3. Har bir service faylida receive(on:) qo'shish
Decode background-da bo'lgach, UI-ni yangilashdan oldin main-ga qaytish kerak.
CoinDataService.swift:
coinSubscription = NetworkingManager.download(url: url)
.decode(type: [CoinModel].self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main) // ← decode KEYIN main-ga o'tadi
.sink(receiveCompletion: NetworkingManager.handleCompletion) { [weak self] returnedCoins in
self?.allCoins = returnedCoins
}
Xuddi shunday CoinDetailDataService, MarketDataService, CoinImageService fayllarida ham decode-dan keyin .receive(on: DispatchQueue.main) qo'shiladi.
Nima uchun muhim? Agar background threaddan
@Publishedo'zgaruvchini yangilasak —"Publishing changes from background threads is not allowed"xatoligi chiqadi. Combine bu qoidani qat'iy qo'llaydi.
4. retry(3) — 3 marta qayta urinish
URLSession.shared.dataTaskPublisher(for: url)
.tryMap({ try handleURLResponse(output: $0, url: url) })
.retry(3) // Agar server xatolik bersa — 3 marta qayta so'rov yuboradi
.eraseToAnyPublisher()
Qanday ishlaydi? Agar
tryMapxatolik qaytarsa —retry(3)avtomatikdataTaskPublisher-ni qaytadan ishga tushiradi. 3 urinishdan keyin ham muvaffaqiyatsiz bo'lsa —handleCompletion-ga xatolik keladi.Hozirgi ilovamizda xatolik yo'q, lekin serverda muammo bo'lganda bu qator foydalanuvchiga uzluksiz tajriba beradi.
5. Bo'sh portfel xabari
Portfelda tangalar bo'lmasa — ekran bo'sh va foydalanuvchi nima qilish kerakligini bilmaydi. HomeView-da:
private var portfolioCoinsList: some View {
ZStack(alignment: .top) {
if vm.portfolioCoins.isEmpty && vm.searchText.isEmpty {
// Bo'sh portfel xabari
portfolioEmptyText
} else {
// Tangalar ro'yxati
List {
ForEach(vm.portfolioCoins) { coin in
CoinRowView(coin: coin, showHoldingsColumn: true)
.onTapGesture { segue(coin: coin) }
}
}
.listStyle(PlainListStyle())
}
}
.transition(.opacity.animation(.easeIn))
}
private var portfolioEmptyText: some View {
Text("Hali portfelingizga tanga qo'shmadingiz! Boshlash uchun + tugmasini bosing 🧐!")
.font(.callout)
.fontWeight(.medium)
.foregroundColor(Color.theme.accent)
.multilineTextAlignment(.center)
.padding(50)
}
6. ContentView-ni o'chirish
Kurs boshida ranglarni sinash uchun yaratilgan ContentView.swift endi kerak emas. O'chirib tashlang — ilova yaxshi ishlashda davom etadi.
7. Portfel foiz hisoblashidagi xatolik tuzatish
HomeViewModel-dagi mapGlobalMarketData funksiyasida percentageChange hisoblashda qo'shimcha × 100 kerak emas:
// Noto'g'ri:
let percentageChange = ((portfolioValue - previousValue) / previousValue) * 100
// To'g'ri — priceChangePercentage24H allaqachon foiz (masalan 13.5):
let percentageChange = (portfolioValue - previousValue) / previousValue
Chunki StatisticModel foizni formatlab ko'rsatadi (13.5 → 13.50%). × 100 qo'shsak, 1350% ko'rinadi — bu noto'g'ri.
Yakuniy holat
Ilovangizni yana bir marta ishga tushiring. Keyin quyidagilarni tekshiring:
| Xususiyat | Holat |
|---|---|
| App ikonkasi | ✅ Ko'rinadi |
| Launch animatsiyasi | ✅ To'lqinlanuvchi harflar |
| Saralash | ✅ Reyting, narx, hisoblar |
| DetailView | ✅ Grafik + statistika + tavsif |
| Core Data | ✅ Portfel saqlanadi |
| iPad | ✅ Stack navigation |
| Background threading | ✅ Decode background-da |
| Network retry | ✅ 3 marta urinish |
| Bo'sh portfel xabari | ✅ Foydalanuvchiga yo'l-yo'riq |
"Butun ilova 27 dars davomida bir nol dan qurildi. MVVM arxitekturasi, Combine, Core Data, custom animations — hammasi SwiftUI-ning kuchi bilan. Bu sizning portfolio uchun juda kuchli loyiha!"
Keyingi darsda rang sxemasini yangilaymiz. 🚀