Статьи

iOS с нуля с Swift: постоянство данных и песочница на iOS

Сохранение данных при запуске приложений — это требование, которое есть в большинстве приложений iOS, от хранения пользовательских настроек в системе по умолчанию до управления большими наборами данных в реляционной базе данных. В этой статье мы рассмотрим наиболее распространенные стратегии, используемые для хранения данных в приложении iOS. Я также расскажу о файловой системе iOS и о том, как песочница в приложениях влияет на сохранность данных.

Ты прошел долгий путь, кузнечик, и ты многому научился. Но есть один жизненно важный аспект разработки под iOS, который мы еще не обсуждали — сохранение данных. Практически каждое приложение iOS хранит данные для последующего использования. Данные, хранящиеся в вашем приложении, могут быть любыми: от пользовательских настроек до временных кэшей или даже больших наборов реляционных данных.

Прежде чем обсуждать наиболее распространенные стратегии сохранения данных на платформе iOS, я собираюсь потратить несколько минут на обсуждение файловой системы и концепции изолированной программной среды приложений. Вы действительно думаете, что можете хранить данные своего приложения в файловой системе где угодно? Подумай еще раз, падаван.

Безопасность на платформе iOS была одним из главных приоритетов Apple с тех пор, как iPhone был представлен в 2007 году. В отличие от приложений OS X, приложение iOS находится в изолированной программной среде. Песочница приложения не только ссылается на каталог песочницы приложения в файловой системе. Он также включает в себя контролируемый и ограниченный доступ к пользовательским данным, хранящимся на устройстве, системным службам и оборудованию.

С появлением Mac App Store Apple начала применять изолированную программную среду приложений в OS X. Хотя ограничения, накладываемые на приложения OS X, не такие строгие, как ограничения, накладываемые на приложения iOS, основная концепция аналогична. Хотя есть и отличия. Например, в изолированной программной среде приложения для iOS содержится пакет приложений, чего нельзя сказать о приложениях OS X. Причины этих различий в основном исторические.

Операционная система устанавливает каждое приложение iOS в каталог песочницы, который содержит каталог пакета приложений и три дополнительных каталога, Documents , Library и tmp . Доступ к каталогу песочницы приложения, часто называемому его домашним каталогом, можно получить, вызвав простую функцию Foundation, NSHomeDirectory() .

1
print(NSHomeDirectory())

Вы можете попробовать это сами. Создайте новый проект Xcode на основе шаблона приложения Single View и назовите его « Постоянство данных».

Настройка проекта

Откройте AppDelegate.swift и добавьте приведенный выше фрагмент кода в application(_:didFinishLaunchingWithOptions:) . Если вы запустите приложение в симуляторе, вывод в консоли должен выглядеть примерно так:

1
/Users/Bart/Library/Developer/CoreSimulator/Devices/14F00EFB-2EAB-438C-B401-7FEFDA1D94AB/data/Containers/Data/Application/81B23594-3BA2-4AF9-B91A-F74A53FD6945

Однако, если вы запустите приложение на физическом устройстве, результат будет выглядеть немного иначе, как вы можете видеть ниже. Однако «песочница» приложения и наложенные ограничения идентичны.

1
/var/mobile/Containers/Data/Application/41E7939B-6A39-4005-9C28-372FD9C7AD99

Получение пути к каталогу документов приложения требует немного больше работы, как вы можете увидеть в следующем фрагменте кода.

1
2
3
4
5
let directories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
 
if let documents = directories.first {
    print(documents)
}

Мы вызываем NSSearchPathForDirectoriesInDomains() , которая определена в платформе Foundation. В качестве первого аргумента мы передаем DocumentDirectory типа NSSearchPathDirectory чтобы указать, что нас интересует только каталог документов приложения. Второй и третий аргументы имеют меньшее значение для нашего обсуждения. Функция возвращает массив типа [String] , содержащий один результат, путь к каталогу документов приложения.

В чем преимущество песочницы? Основная причина для приложений песочницы — безопасность. Ограничивая приложения своей собственной изолированной программной средой, скомпрометированные приложения не могут привести к повреждению операционной системы или других приложений.

Под взломанными приложениями я подразумеваю как взломанные приложения, так и приложения, которые являются преднамеренно вредоносными, а также приложения, которые содержат критические ошибки, которые могут непреднамеренно привести к повреждению.

Даже если приложения находятся в изолированной программной среде на платформе iOS, приложения могут запрашивать доступ к определенным файлам или ресурсам, находящимся вне их изолированной программной среды, через ряд системных интерфейсов. Примером этого является музыкальная библиотека, хранящаяся на устройстве. Знайте, однако, что системные платформы отвечают за любые операции, связанные с доступом к файлам.

Несмотря на то, что вы можете делать практически все, что захотите, в песочнице своего приложения, Apple предоставила несколько рекомендаций относительно того, что и где хранить. Об этих рекомендациях важно знать по нескольким причинам. Когда резервное копирование устройства iOS выполняется на ваш компьютер или в iCloud, не все файлы в песочнице включаются в резервную копию.

Например, каталог tmp следует использовать только для временного хранения файлов. Операционная система может свободно очищать этот каталог в любое время, например, когда на устройстве недостаточно места на диске. Каталог tmp не включен в резервные копии.

Каталог Documents предназначен для пользовательских данных, а каталог Library — для данных приложения, которые не привязаны строго к пользователю. Каталог Caches в каталоге Library — это еще один каталог, для которого нет резервных копий.

Также имейте в виду, что ваше приложение не должно изменять содержимое каталога комплекта приложений. Каталог комплекта приложений подписывается при установке приложения. Изменяя содержимое каталога комплекта приложений любым способом, вышеупомянутая подпись изменяется, что означает, что операционная система не позволяет приложению снова запускаться. Это еще одна мера безопасности, введенная Apple для защиты клиентов.

Существует несколько стратегий хранения данных приложения на диске. В этой статье мы кратко рассмотрим четыре распространенных подхода на iOS:

  • система по умолчанию
  • списки свойств
  • SQLite
  • Основные данные

Опции, описанные в этой статье, не должны рассматриваться как взаимозаменяемые. Каждая стратегия имеет свои преимущества и недостатки. Давайте начнем с рассмотрения системы по умолчанию.

Система по умолчанию — это то, что iOS унаследовала от OS X. Несмотря на то, что она была создана и предназначена для хранения пользовательских настроек, ее можно использовать для хранения любого типа данных, если это тип списка свойств, NSString , NSNumber , NSDate , NSArray , NSDictionary и NSData или любой из их изменяемых вариантов.

А как насчет типов данных Swift? К счастью, Свифт достаточно умен. Он может хранить строки и числа, конвертируя их в NSString и NSNumber . То же самое относится к массивам и словарям Swift.

Система по умолчанию — это не что иное, как набор списков свойств, по одному списку свойств на приложение. Список свойств хранится в папке « Предпочтения » в папке « Библиотека » приложения, что указывает на назначение и функцию списка свойств.

Одна из причин, по которой разработчикам нравится система по умолчанию, заключается в простоте использования. Взгляните на следующий пример, чтобы понять, что я имею в виду.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
let userDefaults = NSUserDefaults.standardUserDefaults()
 
// Setting Values
userDefaults.setBool(true, forKey: «Key1»)
userDefaults.setInteger(123, forKey: «Key2»)
userDefaults.setObject(«Some Object», forKey: «Key3»)
userDefaults.setObject([1, 2, 3, 4], forKey: «Key4»)
 
// Getting Values
userDefaults.boolForKey(«Key1»)
userDefaults.integerForKey(«Key2»)
userDefaults.objectForKey(«Key3»)
userDefaults.objectForKey(«Key4»)
 
userDefaults.synchronize()

NSUserDefaults standardUserDefaults() для NSUserDefaults , NSUserDefaults ссылка на общий объект по умолчанию.

В последней строке мы вызываем synchronize() для общего объекта по умолчанию, чтобы записать любые изменения на диск. Редко необходимо вызывать synchronize() , потому что система по умолчанию сохраняет изменения при необходимости. Однако, если вы сохраняете или обновляете настройки с использованием системы по умолчанию, иногда может быть полезно или необходимо явно сохранить изменения на диске.

На первый взгляд, система по умолчанию кажется не чем иным, как хранилищем значений ключей, расположенных в определенном месте. Однако класс NSUserDefaults , определенный в платформе Foundation, представляет собой нечто большее, чем интерфейс для управления хранилищем значений ключей. Взгляните на его класс ссылки для получения дополнительной информации.

Прежде чем двигаться дальше, вставьте приведенный выше фрагмент кода в метод приложения-делегата application(_:didFinishLaunchingWithOptions:) и запустите приложение в симуляторе. Откройте новое окно Finder и перейдите к « Библиотека»> «Разработчик»> «CoreSimulator»> «Устройства»> «<DEVICE_ID>»> «Данные»> «Контейнеры»> «Данные»> «Приложение»> «<APPLICATION_ID>» .

<DEVICE_ID> и <APPLICATION_ID> — это два идентификатора, уникальных для симулятора и вашего приложения соответственно. Расположение изолированной программной среды приложения для симулятора зависит от используемой версии Xcode. Если вы не используете Xcode 7, путь, вероятно, будет другим.

Вы можете упростить свою жизнь, напечатав путь к домашнему каталогу вашего приложения к консоли XCode. Добавьте следующий оператор печати в application(_:didFinishLaunchingWithOptions:) .

1
print(NSHomeDirectory())

Папка с загадочным именем является каталогом изолированной программной среды приложения. В каталоге изолированной программной среды приложения откройте папку « Установки », расположенную в папке « Библиотека », и проверьте ее содержимое.

Песочница для приложений

Вы должны увидеть список свойств с именем, идентичным идентификатору пакета приложения. Это хранилище по умолчанию для вашего приложения. Вот как выглядит список свойств в текстовом редакторе. Как видите, список свойств хранится на диске в виде файла XML.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<?xml version=»1.0″ encoding=»UTF-8″?>
<!DOCTYPE plist PUBLIC «-//Apple//DTD PLIST 1.0//EN» «http://www.apple.com/DTDs/PropertyList-1.0.dtd»>
<plist version=»1.0″>
<dict>
    <key>Key1</key>
    <true/>
    <key>Key2</key>
    <integer>123</integer>
    <key>Key3</key>
    <string>Some Object</string>
    <key>Key4</key>
    <array>
        <integer>1</integer>
        <integer>2</integer>
        <integer>3</integer>
        <integer>4</integer>
    </array>
</dict>
</plist>

Если вы хотите упростить доступ к песочнице приложения в симуляторе, я рекомендую вам взглянуть на SimPholder . Это небольшая утилита, которая делает работу с симулятором намного, намного проще.

Мы уже рассмотрели списки свойств в этой серии. По сути, резервное хранилище базы данных по умолчанию для пользователя является списком свойств. Использование списков свойств — это удобная стратегия для хранения и извлечения графа объектов. Списки свойств издавна существуют, просты в использовании и, следовательно, являются отличным вариантом для хранения данных в приложении iOS.

Как я упоминал ранее, важно иметь в виду, что список свойств может хранить только данные списка свойств. Означает ли это, что невозможно сохранить объекты пользовательской модели, используя списки свойств? Это возможно Однако, объекты пользовательской модели должны быть заархивированы — форма сериализации — прежде чем они могут быть сохранены в списке свойств. Архивация объекта просто означает, что объект необходимо преобразовать в тип данных, который можно сохранить в списке свойств, например в экземпляре NSData .

Вы помните протокол NSCoding , определенный в платформе Foundation? Протокол NSCoding определяет два метода: init(coder:) и encodeWithCoder(_:) . Класс реализует эти методы, чтобы обеспечить возможность кодирования и декодирования экземпляров класса.

Кодирование и декодирование являются основными механизмами архивирования и распространения объектов. Как объекты архивных работ станут понятны чуть позже в этой серии. В этом уроке я только покажу вам, как записывать массивы и словари на диск, используя списки свойств.

Следующий фрагмент кода должен дать вам представление о том, как легко записать массив или словарь на диск. Теоретически, граф объектов, хранящийся в списке свойств, может быть настолько сложным или настолько большим, насколько вы захотите. Однако имейте в виду, что списки свойств не предназначены для хранения десятков или сотен мегабайт данных, поэтому попытка использовать их таким образом, вероятно, приведет к снижению производительности.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let directories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
 
if let documents = directories.first {
    if let urlDocuments = NSURL(string: documents) {
        let urlFruits = urlDocuments.URLByAppendingPathComponent(«fruits.plist»)
        let urlDictionary = urlDocuments.URLByAppendingPathComponent(«dictionary.plist»)
         
        // Write Array to Disk
        let fruits = [«Apple», «Mango», «Pineapple», «Plum», «Apricot»] as NSArray
        let dictionary = [«anArray» : fruits, «aNumber» : 12345, «aBoolean» : true] as NSDictionary
         
        fruits.writeToFile(urlFruits.path!, atomically: true)
        dictionary.writeToFile(urlDictionary.path!, atomically: true)
         
        // Load from Disk
        let loadedFruits = NSArray(contentsOfURL: urlFruits)
        if let fruits = loadedFruits {
            print(fruits)
        }
         
        let loadedDictionary = NSDictionary(contentsOfURL: urlDictionary)
        if let dictionary = loadedDictionary {
            print(dictionary)
        }
    }
}

Давайте посмотрим на приведенный выше фрагмент кода. Мы начнем с сохранения ссылки на литерал массива в переменной с именем fruits . Мы создаем файл URL для хранения списка свойств, который мы собираемся сделать. URL-адрес файла создается путем добавления строки к URL-адресу файла каталога Documents . Добавляемая строка является именем списка свойств, который мы создаем за секунду, включая его расширение .plist .

writeToFile(_:atomically:) массив на диск так же просто, как вызвать writeToFile(_:atomically:) для массива. Вы можете пока игнорировать atomically флаг. Как показывает пример, запись словаря на диск происходит по аналогичной схеме. В примере также показано, как создавать массивы и словари из списка свойств, но это то, что мы уже рассмотрели ранее в этой серии. Запустите приложение в симуляторе и перейдите в каталог документов приложения. В этом каталоге вы должны увидеть два списка свойств, которые мы только что создали.

Запись объектов в список свойств

Вот как выглядит список свойств словаря, когда вы открываете его в текстовом редакторе.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<?xml version=»1.0″ encoding=»UTF-8″?>
<!DOCTYPE plist PUBLIC «-//Apple//DTD PLIST 1.0//EN» «http://www.apple.com/DTDs/PropertyList-1.0.dtd»>
<plist version=»1.0″>
<dict>
    <key>aBoolean</key>
    <true/>
    <key>aNumber</key>
    <integer>12345</integer>
    <key>anArray</key>
    <array>
        <string>Apple</string>
        <string>Mango</string>
        <string>Pineapple</string>
        <string>Plum</string>
        <string>Apricot</string>
    </array>
</dict>
</plist>

Если ваше приложение управляется данными и работает с большими объемами данных, вы можете захотеть изучить SQLite. Что такое SQLite? Слоган на веб-сайте SQLite гласит: «Маленький. Быстрый. Надежный. Выберите любые три». Это прекрасно подводит итог.

SQLite — это библиотека, которая реализует облегченную встроенную реляционную базу данных. Как следует из его названия, он основан на стандарте SQL (язык структурированных запросов ), как и MySQL и PostgreSQL.

Основное отличие от других баз данных SQL заключается в том, что SQLite является переносимым и очень легковесным. Вместо отдельного процесса, доступ к которому осуществляется из клиентского приложения, SQLite является бессерверным. Другими словами, он встроен в приложение или управляется системой, в которой выполняется приложение, что означает, что он очень быстрый.

Веб-сайт SQLite утверждает, что это самая распространенная база данных SQL. Я не знаю, так ли это до сих пор, но это, безусловно, популярный выбор для хранения данных на стороне клиента. Преимущество SQLite перед работой непосредственно с объектами состоит в том, что SQLite намного, намного быстрее. Во многом это связано с тем, что реляционные базы данных и языки объектно-ориентированного программирования существенно различаются.

Чтобы преодолеть разрыв между SQLite и Objective-C, с течением времени было создано несколько решений Object Relational Mapping (ORM). ORM, созданный Apple для iOS и OS X, называется Core Data , и мы рассмотрим его позже в этом уроке.

Использование SQLite на iOS означает работу с библиотекой на Си. Если вы предпочитаете объектно-ориентированное решение, я настоятельно рекомендую оболочку Objective-C Гуса Мюллера ( Flying Meat, Inc. ) для SQLite, FMDB .

Библиотека очень производительная. Я использовал FMDB в прошлом и был очень доволен его API, надежностью и надежностью библиотеки.

Разработчики, плохо знакомые с Core Data, часто ошибочно принимают Core Data за базу данных, хотя это действительно решение для реляционного отображения объектов, созданное и поддерживаемое Apple. Мэтт Галлахер написал отличный пост о различиях между Core Data и базой данных. Базовые данные предоставляют реляционную объектно-ориентированную модель, которую можно сериализовать в хранилище XML, двоичное или SQLite. Core Data даже поддерживает хранилища в памяти.

Почему вы должны использовать Core Data вместо SQLite? Задавая этот вопрос, вы ошибочно полагаете, что Core Data — это база данных. Преимущество использования Core Data заключается в том, что вы работаете с объектами, а не с необработанными данными, такими как строки в базе данных SQLite или данные, хранящиеся в файле XML. Несмотря на то, что у Core Data было несколько трудных лет, когда она была впервые выпущена, она превратилась в надежную среду со многими функциями, включая автоматическую миграцию, отслеживание изменений, сбои и встроенную проверку.

Еще одна замечательная особенность, которую многие разработчики ценят — это редактор модели Core Data, встроенный в Xcode. Редактор позволяет разработчикам моделировать модель данных приложения через графический интерфейс.

Редактор базовой модели данных

Является ли Core Data правильным решением для вашего приложения, зависит от данных, которыми вы планируете управлять, как с точки зрения объема данных, так и базовой модели. Если вы планируете управлять очень большими наборами данных, то со временем Core Data может стать узким местом в производительности. В этом случае SQLite может быть лучшим решением.

Вы, наверное, слышали об iCloud, и вам может быть интересно, где iCloud вписывается в историю сохранения данных. В отличие от SQLite и Core Data, iCloud не является формой сохранения данных. Вместо этого это платформа или сервис для предоставления пользовательских данных на нескольких устройствах и нескольких экземплярах приложения или даже семейства приложений.

Платформа iCloud включает в себя несколько сервисов или компонентов. Интересующий нас компонент — iCloud Storage , который включает в себя четыре типа хранилищ:

  • CloudKit
  • хранилище ключей
  • хранение документов
  • Базовое хранилище данных

Если вы хотите узнать больше о iCloud Storage, я рекомендую прочитать мою серию о iCloud Storage .

Теперь вы должны иметь представление о вариантах хранения данных при разработке для платформы iOS. Имейте в виду, что не все стратегии, которые мы рассмотрели, равны.

Эта серия постепенно подходит к концу. В следующих двух статьях мы создадим еще одно приложение, чтобы применить на практике то, что мы узнали на данный момент. Лучший способ учиться — это делать.

Если у вас есть какие-либо вопросы или комментарии, вы можете оставить их в комментариях ниже или обратиться ко мне в Twitter .