Най-накрая е време да научим как да запазваме данните си! Страхотен! Това беше отдавна!

iOS прави спестяването на данни лесно! Е… поне за потребителите, защото някои страхотни разработчици прекараха времето си в намиране на начин да ги накарат да забравят, че трябва да го правят!

Все още си спомням ужасните дни като потребител на Windows (или когато използвам Office за Mac, дори версията 2019, или всяко друго приложение за Mac, което не е разработено на Mac), когато напусках програма и получавах известието, че ако имах ако не беше запазен напредъкът ми, нещата щяха да бъдат загубени!

Настройвам

Аз самият опитвам нещо ново тук: в Xcode се разклонявам от главния клон към нов клон, наречен „project12a“.

Надяваме се, че всички мои промени ще бъдат запазени в новия клон и когато искам да се върна, просто ще трябва да превключа към „главния“.

Напълно нов в това, така че... пожелайте ми успех!

Основи на четенето и писането: UserDefaults

Тази първа част беше просто за опознаване на UserDefaults и неговата работа. Това е подклас на NSObject, описан като „интерфейс към базата данни по подразбиране на потребителя, където съхранявате постоянно двойки ключ-стойност при стартирания на вашето приложение“. Тази част от дискусията на документацията ми се струва важна:

По време на изпълнение използвате UserDefaults обекти, за да прочетете настройките по подразбиране, които вашето приложение използва от базата данни по подразбиране на потребителя. UserDefaults кешира информацията, за да избегне необходимостта да отваряте базата данни по подразбиране на потребителя всеки път, когато имате нужда от стойност по подразбиране. Когато зададете стойност по подразбиране, тя се променя синхронно в рамките на вашия процес и асинхронно към постоянното хранилище и други процеси.

Променливата на класа .standard връща споделения обект по подразбиране и, ако все още не съществува, създава такъв. След това можем да съхраним всичко това в свойство и да го използваме, за да напишем някои набори от предпочитания, използвайки метода .set(_ value: Any?, forKey defaultName: String).

Ако искаме да четем предпочитанията, вместо това трябва да използваме метода .objectType(forKey:), запълвайки objectType с всичко, което може да търсим, от Int до String до каквото и да е друго.

Директното използване на .object(forKey:) създава някои проблеми, защото получаваме незадължителна стойност в замяна и трябва да я преобразуваме и евентуално да нула кохалестираме до стойност по подразбиране, в случай че може да не съществува.

Коригиране на проект 10: NSCoding

Нека направим още малко магия сега: научаваме, че можем да записваме всякакви данни в UserDefaults, стига да следваме някои правила. Накратко:

  • ние използваме класа NSKeyedArchiver, който е дефиниран като “Кодер, който съхранява данните на обект в архив, посочен от ключове”.
  • наричаме неговия archiveData метод, който, странно, е описан като отхвърлен в документацията... но какво заема неговото място? Каквото и да е, това трансформира обектна графика (т.е.: този обект плюс всичките му препратки) в Data обект и го записва в UserDefaults.
  • данните могат да бъдат от всякакъв тип, стига да са един от по-простите типове или ако съответстват на протокола NSCoding.

И така, в проект 10 добавяме съответствие към протокола NSCoding в горната част на класа Person. Обясняват ни, че структурите не могат да съответстват на NSCoding… Не съм сигурен, че разбирам това, но може би не е нужно. Просто ще кажа на мозъка си, че „ако искам да запазя нещо, трябва да направя това нещо клас, който наследява от NSObject и който съответства на NSCoding“. Месечен цикъл!

Съответствието на протокола носи със себе си своите проблеми и затова трябва да добавим метода encode, който ще кодира (т.е.: запазва) нашето име и изображение зад правилно именувани ключове (изглежда като речник и може би просто е ) и required init—това е инициализатор, който подкласовете също ще трябва да добавят, ако наследят от този клас—с присвояване на нашите свойства name и image към извикване на метода decodeObject(forKey:). Също така решаваме да играем на сигурно и да добавим операция за нулево обединяване, в случай че данните не бъдат намерени.

Накратко:

инициализаторът се използва при зареждане на обекти от този клас и encode() се използва при запис

Обратно в ViewController.swift сега трябва да напишем действителния код, който записва и зарежда, но се нуждаем от Data и имаме масиви, така че... изключено... първо трябва да конвертираме нещата.

За наше щастие ние „просто“ трябва да добавим нов save() метод, където по желание се опитваме да съхраним върнатия резултат от извикването на NSKeyedArchiver.archivedData в масива на нашия people в незадължителна обвързана константа и, ако това успее, ще настроим това да бъде запазено в нашето потребителско хранилище по подразбиране! Лесно нали? …да…!

Сега трябва да извикаме save() навсякъде, където презареждахме нашите данни за колекция-изглед и накрая да позволим зареждането в viewDidLoad(), така че когато стартираме приложението следващия път, всъщност да заредим същите данни като последния път.

И така, в viewDidLoad(), ние по избор свързваме върнатия обект на defaults.object(forKey:)—използвайки същия ключ, който използвахме, за да запазим данните, разбира се—към константа и по избор го преобразуваме към Data обект. Ако това успее, ние по избор се опитваме да съхраним резултата от извикването на NSKeyedUnarchiver.unarchiveTopLevelObjectWithData() — опитахте ли да го кажете на един дъх — по избор надолу като масив от Persons в константа и, ако това също успее, задаваме нашата променлива people на тази последна постоянно! Ако, не дай си Боже, нещо се обърка, просто зареждаме празен [Person].

Коригиране на проект 10: Codable

Ако пишем само Swift, Codable е правилният начин. Разликите между Codable и NSCoding са следните:

  • Codable работи както върху класове, така и върху структури.
  • Няма нужда сами да пишем encode() и decodeObject().
  • Вградена поддръжка за четене/запис на JSON.

Не е лошо, нали?

Първо, излязох от моя клон master и създадох нов клон от него, наречен project12a (започнах да усещам опасна сила, течаща във вените ми!) и добавих съответствие към Codable за класа Person. И всъщност тук няма какво друго да се прави...! невероятно!

Обратно в ViewController.swift създаваме метод save(). Вътре ние декларираме и инициализираме нов JSONEncoder(), като по желание се опитваме да свържем (по избор) резултата от извикването към метода encode на този енкодер към константа и, ако това успее, задаваме запазените данни за съответния ключ в нашите потребителски настройки по подразбиране стандартно съхранение. Само за безопасност, ако нещо се обърка, отпечатваме съобщение на конзолата.

Както и преди, наричаме този метод save() навсякъде, където презареждаме нашите данни.

След това преминаваме към viewDidLoad(), създаваме извикване към нашия UserDefaults.standard път, по избор свързваме незадължителния downcast към Data резултат от извикването на метода object(forKey:) на нашия ключ „people“ и, ако това успее – дъх!… – декларираме нов JSONDecoder() и настройте do-catch блок, за да се опитате да създадете масив от Person обекти от данните, извлечени преди това.

Този последен пасаж заслужава малко пояснение: методът object(forKey:) изважда незадължителен Data, който се разопакова с if let и as?. След това JSONDecoder го преобразува обратно в обектна графика—т.е. нашето [Person].

Това е! Можете да намерите кода за трите версии на проекта тук.

Моля, уведомете ме, ако нещо не е наред или подозрително не е наред.

Моля, не забравяйте да поздравите и благодарите на Пол за цялата му страхотна работа (можете да го намерите в Twitter) и не забравяйте да посетите 100 дни На Swiftинициатива страница.