Published on

UserDefaults yordamida ma'lumotlarni saqlash va doimiy qilish

Authors

Ushbu kursda hozirgacha ilovamizning katta qismini qurib bo'ldik β€” ilovamiz to'liq ishlamoqda. Ammo payqagan bo'lishingiz mumkin: u, aslida, hech qanday ma'lumotni saqlamayapti. Agar ilovani yopib, qaytadan ochsak, qo'shgan yangi vazifa (to-do) elementlarimiz saqlanib qolmaydi. Ushbu videoda esa biz tezda UserDefaultsni qo'shamiz, shunday qilib vazifalar ro'yxatimizdagi elementlar sessiyalar orasida saqlanib, doimiy (persist) bo'lib qoladi.


Muammo: ma'lumot saqlanmayapti

Hozirgacha biz kodning katta qismini, ilovaning katta qismini yozib bo'ldik β€” bizda ekranlarimiz bor, ilovamizga element qo'sha olamiz, elementlarni o'chira olamiz, ularning bajarilganlik holatini almashtira olamiz. Ammo payqagan bo'lishingiz mumkin bo'lgan bir narsa bor: agar shu ro'yxatni tahrirlab, ilovani yopib, qaytadan ochsak, u boshlang'ich holatga qaytib ketadi β€” hozircha unda init chaqirilganida, getItems chaqirilganida yaratiladigan o'sha uchta ItemModel bor, va biz hozirgina qilgan barcha ish β€” element qo'shish, yangilash β€” saqlanmayapti, chunki biz buni hech qayerga saqlamadik, ma'lumotni doimiy qilmadik.

Shuning uchun bizga items massivimizni, uni o'zgartirganimizda, yangilaganimizda, saqlaydigan joy kerak. Agar bu App Store'ga qo'yiladigan ilova bo'lganida, ehtimol buning uchun Core Datadan foydalanardim, ammo men SwiftUI bootcamp kursida Core Data'ni qamrab olmaganman. Shuning uchun Core Data'dan ham soddaroq β€” biz shunchaki UserDefaultsdan foydalanamiz.


UserDefaults qachon ishlatiladi

Davom etishdan oldin, tezda shuni ta'kidlab o'tmoqchiman: UserDefaults asosan kichik hajmdagi ma'lumotlar uchun ishlatilishi kerak β€” masalan, foydalanuvchi ID'si yoki hozirgi foydalanuvchining ismi kabi narsalar uchun. Bizning holatimizda esa, ItemModelimiz ham ancha kichik ma'lumot β€” bu shunchaki sarlavha va Boolean, xolos, va bizda bir nechtagina element bor, vazifalar ro'yxatimizda minglab yoki yuz minglab turli element yo'q. Shuning uchun UserDefaults ishlatishimiz katta muammo emas β€” ammo agar kattaroq ma'lumotlar bazasi bo'lganida, sizlarga shuni bilib qo'yishni xohlardim: UserDefaults haqiqatan ham faqat kichik hajmdagi ma'lumotlar uchun ishlatilishi kerak, va katta ma'lumotlar to'plamlarini UserDefaults'ga joylashtirishni boshlamang. Shunga qaramay, UserDefaults ishlatish juda oson, va u bizning hozirgi vazifamiz uchun mukammal mos keladi.


saveItems funksiyasini yaratish

Endi ListViewModelimizning pastiga yangi funksiya yaratamiz. updateItemdan keyin, func saveItems() deb yozib, qavslarni ochib-yopamiz.

Men SwiftUI bootcamp kursida AppStorage haqida butun bir video qilgan edim, va ehtimol nega hozir AppStorage ishlatmayotganimiz haqida o'ylayotgan bo'lishingiz mumkin β€” buning sababi shu: biz hozir bir class ichidamiz. AppStorage'dan to'g'ridan-to'g'ri view ichida foydalanish kerak, ammo biz class ichida bo'lganimiz uchun, UserDefaultsdan foydalanish ancha to'g'ri yechim.

UserDefaults.standard.set(...)ni chaqiramiz. Bu yerda qo'shishimiz mumkin bo'lgan qiymat turlariga qarasak, ularning hech biri aniq ItemModel deb yozilmaganini ko'ramiz β€” buning sababi shu: UserDefaults ItemModel nima ekanligini umuman bilmaydi, va biz ItemModelni to'g'ridan-to'g'ri UserDefaults'ga saqlay olmaymiz.

Shuning uchun bu kodni o'chirib, buning o'rniga biz bu barcha ItemModellarni JSON ma'lumotiga (JSON data) aylantiramiz. Agar web-dasturchi bo'lgan bo'lsangiz, JSON ma'lumoti nima ekanligini bilishingiz mumkin β€” bu, asosan, ma'lumot bloki, xolos. Biz ItemModelimizni olib, uni shu ma'lumot blokiga aylantiramiz, shu ma'lumotni UserDefaults'ga joylaymiz, va uni qaytarib olganimizda esa, shu ma'lumotni yana ItemModellarga aylantiramiz.


ItemModel-ni Codable-ga moslashtirish

Buni amalga oshirish uchun, avval ItemModelga o'tamiz ("Jump to Definition" orqali). Bizga qilishimiz kerak bo'lgan yagona narsa β€” buni Codablega moslashtirish: vergul qo'yib, Codable deb yozamiz.

Keyingi seriyada Codable haqida butun bir video qilaman, chunki bu yerda "sahna ortida" ko'plab narsa sodir bo'ladi, va bu juda kuchli vosita β€” ammo hozircha bilishingiz kerak bo'lgan yagona narsa shu: ItemModel endi Codable'ga mos kelgani uchun, biz bu elementni decode va encode qila olamiz. Demak, biz ItemModelni, asosan, ma'lumotga (data) aylantirishimiz, va ma'lumotdan yana ItemModelga qaytarishimiz mumkin bo'ladi:

struct ItemModel: Identifiable, Codable {
    ...
}

Items massivini JSON ma'lumotiga aylantirish

Endi saveItems funksiyamizga qaytamiz. Bizda items massivimiz bor β€” bu ItemModel massivi β€” va uni ma'lumotga (data) encode qilishga harakat qilamiz.

Juda sodda tarzda: if let encodedData = try? JSONEncoder().encode(items) { ... } deb yozamiz. Demak, biz JSONEncoder yaratamiz β€” bu narsalarni JSON ma'lumotiga aylantira oladi β€” va shu encoder'ga items massivimizni beramiz. if let bayonoti ichidamiz, shuning uchun JSON encoder uchun try? usulidan foydalanishimiz kerak β€” bu, asosan, shu amalni bajarishga harakat qiladi, va bu muvaffaqiyatsiz tugashi ham mumkin, ammo bu joiz, aynan shuning uchun biz if let bayonoti ichidamiz.

Agar bu muvaffaqiyatli bo'lsa, biz JSON encoder yaratgan bo'lamiz β€” bu narsalarni JSON ma'lumotiga aylantira oladigan vosita β€” va shu yerda items massivimizni encode qilamiz: demak, items massivimiz ItemModel massividan JSON ma'lumotiga aylanadi, va aynan shu β€” bizning encodedDatamizdir. Endi shu encodedDatani UserDefaults'ga joylaymiz: UserDefaults.standard.set(...) ni chaqiramiz, value: Any parametriga encodedDatani beramiz, key uchun esa UserDefaults uchun bir kalit kerak β€” buni "items_list" deb ataymiz:

func saveItems() {
    if let encodedData = try? JSONEncoder().encode(items) {
        UserDefaults.standard.set(encodedData, forKey: "items_list")
    }
}

Kalitni alohida o'zgaruvchiga chiqarish

Endi bu kalitga, ma'lumotni UserDefaults'dan qaytarib olmoqchi bo'lganimizda, yana murojaat qilishimiz kerak bo'ladi. Bu kalitni shu yerga to'g'ridan-to'g'ri yozib qo'yishim menga unchalik yoqmaydi β€” chunki keyinroq shu kalitga yana murojaat qilishimiz kerak bo'lsa, va ehtimol uni xato yozib qo'ysak yoki nima yozganimizni unutib qo'ysak, bu muammolarga olib kelishi mumkin. Shuning uchun bu kalitni alohida, mustaqil o'zgaruvchiga chiqaraman.

Yuqorida, items massivimizning ostida, let itemsKey: String = "items_list" deb yozamiz. Endi shu itemsKeyni olib, uni shu yerda key sifatida ishlatamiz:

let itemsKey: String = "items_list"

func saveItems() {
    if let encodedData = try? JSONEncoder().encode(items) {
        UserDefaults.standard.set(encodedData, forKey: itemsKey)
    }
}

saveItems-ni avtomatik chaqirish: didSet

Endi bizda items massivimizni ma'lumotga aylantirib, shu ma'lumotni UserDefaults'ga joylaydigan funksiyamiz bor, va bizga qolgan yagona narsa β€” kodimizning biror joyidan saveItemsni haqiqatan ham chaqirish. Men shu UserDefaults'ning ro'yxatimizda sodir bo'layotgan har bir o'zgarish bilan har doim yangilangan va mos bo'lishiga ishonch hosil qilmoqchiman.

Demak, agar element o'chirsak β€” saveItemni chaqirmoqchiman; element ko'chirsak, element qo'shsak, element yangilasak β€” har doim saveItemsni chaqirmoqchiman. Buni shu funksiyalarning har birining oxiriga qo'shishim ham mumkin edi, va bu ham ishlardi β€” ammo men buni bekor qilib, biroz boshqacharoq yo'l tutaman: biz, aslida, shu items massivini o'zgartirganimizda β€” bu massivni barcha shu funksiyalarda o'zgartirib turamiz β€” har safar shu massiv o'zgarganda saveItemsni chaqirishni xohlaymiz.

Shuning uchun bu funksiyalarning har biriga alohida-alohida qo'shishdan ko'ra ancha samaraliroq yo'l bor: yuqoriga, items massivimizning o'ziga qaytamiz. Uning oxiriga qavslarni ochib, Swift'ning didSet deb ataladigan funksiyasidan foydalanamiz, va qavslarni ochamiz.

didSet, asosan, har safar biz shu itemsni o'rnatganimizda β€” ya'ni har safar shu items massivini o'zgartirganimizda β€” chaqiriladi. Shuning uchun shu yerga shunchaki saveItems()ni chaqiraman:

@Published var items: [ItemModel] = [] {
    didSet {
        saveItems()
    }
}

Bu shunchaki shu β€” endi shu items massiviga ta'sir qiluvchi har qanday amal (o'chirish, ko'chirish, qo'shish, yangilash) bajarilganda, biz har doim saveItemsni chaqirib turamiz, bu esa aynan biz xohlagan natija.


getItems: saqlangan ma'lumotni qaytarib olish

Endi bizga qilishimiz kerak bo'lgan so'nggi narsa β€” getItems, chunki har doim shu uchta soxta elementni olish o'rniga, biz, aslida, UserDefaults'da saqlangan elementlarni olib, ularni qo'shmoqchimiz. Shu qatorlarning barchasini belgilab, Command va teskari chiziqcha (/) bosib izohga olib qo'yamiz, chunki ular endi bizga kerak emas.

Keling, elementlarni UserDefaults'dan olishga harakat qilaylik. Avval bizga UserDefaults'dan ma'lumotni olishimiz kerak: let data = UserDefaults.standard.data(forKey: itemsKey) deb yozamiz β€” kalitimiz allaqachon bor, chunki uni konstanta sifatida yaratgan edik, shuning uchun shu yerga itemsKeyni qo'shamiz.

Agar Option tugmasini bosib, dataning ustiga bossak, bu optional ekanligini ko'ramiz β€” chunki shu kalit bo'yicha hech narsa saqlanmagan bo'lishi ham mumkin. Shuning uchun buni xavfsiz tarzda "ochishimiz" kerak: shunchaki let data o'rniga, guard let data = ... else { return } deb yozamiz.

Demak, bu shu kalit bo'yicha ma'lumotni olishga harakat qiladi, va agar bu rost bo'lsa, qolgan kod ishga tushadi; agar yolg'on bo'lsa, funksiyadan chiqib ketadi (return), chunki bizga davom etish uchun aynan shu ma'lumot kerak.


Ma'lumotni qaytadan ItemModel-larga aylantirish (decode)

Keyingi qadam β€” shu ma'lumotni JSON ma'lumot blokidan, qaytadan ItemModel massiviga aylantirish. Buning uchun, asosan, yuqorida if let bayonotida qilgan ishimizning teskarisini qilamiz: u yerda biz JSON encoder olib, elementlarni encode qilgan edik, endi esa JSON decoder bilan, ma'lumotdan yana elementlarga decode qilamiz.

let savedItems = try? JSONDecoder().decode(...) deb yozamiz, va endi unga qaysi ma'lumot turiga decode qilishni xohlashimizni aytishimiz kerak. Bu Decodable protokoliga mos kelishi kerak, va biz ItemModelni avvalroq Codable'ga moslashtirgan edik, shuning uchun shu yerda ItemModeldan foydalanishimiz mumkin β€” ammo bilamizki, savedItems aslida ItemModel massivi bo'ladi, shuning uchun [ItemModel] deb yozamiz, va bu haqiqiy massiv emas, balki shu massivning turi ekanligini bildirish uchun .self qo'shamiz. So'ngra from: datani qo'shamiz β€” bu aynan biz hozirgina olgan ma'lumotimiz:

let savedItems = try? JSONDecoder().decode([ItemModel].self, from: data)

Agar Option tugmasini bosib, savedItemsning ustiga bossak, endi bu bizga [ItemModel]? β€” ya'ni optional ItemModel massivini berayotganini ko'ramiz. Demak, bu yana optional, va biz haqiqatan ham elementlarimiz mavjudligiga ishonch hosil qilmoqchimiz.


guard let bayonotlarini birlashtirish

Shuning uchun yana bir guard let ... else { return } qo'shishimiz mumkin. Bu guard bayonotlarining qiziq tomoni shunda: biz, aslida, bir nechtasini birlashtirib ishlatishimiz mumkin. Birinchisida biz ma'lumotimiz borligini tekshiryapmiz, ikkinchisida esa shu ma'lumotni elementlarga aylantira olishimizni tekshiryapmiz β€” ammo aslida ikkala bayonotni ham birlashtirishimiz mumkin.

Buning uchun, shu birinchi letdan oldin Enter bosaman, va shu elsedan oldin ham Enter bosaman. Shuning uchun avval guard let data = ... deb yozamiz, vergul qo'yamiz, so'ngra savedItems = ... qatorimizni nusxalab, shu yerga joylaymiz.

Endi shu guard bayonoti, aslida, ikkalasini ham birlashtirgan holda saqlamoqda, va ikkinchisini o'chirib tashlashimiz mumkin:

guard let data = UserDefaults.standard.data(forKey: itemsKey),
      let savedItems = try? JSONDecoder().decode([ItemModel].self, from: data) else {
    return
}

Demak, agar ma'lumotni yoki elementlarni ololmasak, funksiyadan chiqib ketamiz; ammo ikkalasini ham olsak, kod davom etadi.

Endi Option tugmasini bosib, savedItemsning ustiga bossak, bu endi optional emas β€” bu haqiqiy ItemModel massivi. Shuning uchun shunchaki self.items = savedItemsni chaqiramiz β€” endi items massivimiz shu saqlangan ma'lumot bilan yangilanadi:

func getItems() {
    guard let data = UserDefaults.standard.data(forKey: itemsKey),
          let savedItems = try? JSONDecoder().decode([ItemModel].self, from: data) else {
        return
    }

    self.items = savedItems
}

Eski, izohga olingan kodni endi o'chirib tashlashim mumkin β€” siz xohlasangiz, buni kodingizda qoldirishingiz ham mumkin.


Sinab ko'rish

Simulyatorda "Play" tugmasini bosib, ishlayotganini tekshirib ko'raylik. Hozircha ekranimizda hech narsa yo'q, chunki biz endi o'sha uchta soxta elementni qo'shmayapmiz. Keling, ba'zi elementlar qo'shishni boshlaylik: "My first item"ni qo'shib, saqlaymiz; "Second item"ni qo'shib, saqlaymiz. Birinchi elementni bajarilgan qilib belgilaymiz, so'ngra ularni tahrirlab, birinchi elementni "second"ga o'zgartirib, "Done" bosamiz.

Endi ilovani yopib, qaytadan ochaylik β€” va ko'ramizki, elementlarimiz haqiqatan ham saqlangan va doimiy bo'lib qolgan! Buning sababi shu: har safar shu elementlarni tahrirlaganimizda, biz ularni UserDefaults'ga saqlab qo'yamiz, va ilovamizni ochganimizda esa, biz shu saqlangan ma'lumotni UserDefaults'dan olib, ilovamizga qaytarib joylaymiz.


Xulosa

Umid qilamanki, bu juda chalkash bo'lib ketmadi β€” try? decode kabi narsalar biroz murakkab ko'rinishi mumkin, ammo biz qilayotgan yagona ish β€” ItemModel massivini JSON ma'lumotiga, va shu JSON ma'lumotidan yana elementlarga aylantirish, xolos.

Shuningdek, sizlarga guard let bayonotlari, hamda if let bayonotlari bilan ham amaliyot qilish imkonini berishni xohladim β€” chunki, ko'p hollarda, if let yoki guard letdan foydalanish bir-biriga almashinadigan (interchangeable) narsa: biz bu yerda ham guard bayonotidan foydalanishimiz mumkin edi, va shu yerda ham if let bayonotidan foydalanishimiz mumkin edi. Ammo endi siz ikkalasi bilan ham amaliyot qildingiz, va kodimiz juda samarali va toza ko'rinmoqda.

Biz ushbu ilovani deyarli tugatdik.

Buy mea coffee