Статьи

Базовые данные и Swift: базовый стек данных

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

Несмотря на то, что Core Data в значительной степени зависит от среды выполнения Objective C и аккуратно интегрируется с платформой Core Foundation, вы можете легко использовать эту платформу в своих проектах Swift. В результате получается простая в использовании инфраструктура для управления графом объектов, которая элегантна в использовании и невероятно эффективна с точки зрения использования памяти.

Платформа Core Data сама по себе не сложна, но если вы новичок в разработке под iOS или OS X, то я рекомендую вам сначала ознакомиться с нашей серией статей о разработке под iOS . Он обучает вас основам разработки под iOS, и в конце серии у вас будет достаточно знаний, чтобы заняться более сложными темами, такими как Core Data.

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

Каждый компонент инфраструктуры базовых данных имеет определенную цель и функцию. Если вы попытаетесь использовать Базовые Данные таким образом, для чего они не были предназначены, вы неизбежно столкнетесь с фреймворком.

То, что я освещаю в этой серии о Core Data, применимо к iOS 7+ и OS X 10.10+, но основное внимание будет уделено iOS. В этой серии я буду работать с Xcode 7.1 и Swift 2.1. Если вы предпочитаете Objective-C, то я рекомендую прочитать мои предыдущие серии по платформе Core Data .

Поначалу инфраструктура Core Data может показаться сложной, но API интуитивно понятен и лаконичен, как только вы поймете, как различные части головоломки сочетаются друг с другом. И именно поэтому большинство разработчиков сталкиваются с проблемами при использовании инфраструктуры. Они пытаются использовать Базовые Данные, прежде чем они увидят эту пресловутую головоломку, они не знают, как кусочки головоломки соединяются и связаны друг с другом.

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

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

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

Что такое Core Data, если это не база данных? Базовые данные — это уровень модели вашего приложения в самом широком смысле. Это модель в шаблоне Model-View-Controller , которая пронизывает SDK iOS.

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

Как я упоминал ранее, стек Core Data является сердцем Core Data. Это коллекция объектов, которые делают Core Data галочкой. Ключевыми объектами стека являются модель управляемого объекта , координатор постоянного хранилища и один или несколько контекстов управляемого объекта . Давайте начнем с краткого обзора каждого компонента.

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

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

Как видно из его имени, объект NSPersistentStoreCoordinator сохраняет данные на диске и обеспечивает совместимость постоянного хранилища и модели данных. Он является посредником между постоянным хранилищем (ями) и контекстом (ами) управляемого объекта, а также заботится о загрузке и кэшировании данных. Это верно. Базовые данные имеют встроенное кэширование.

Постоянный координатор хранилища является проводником оркестра Core Data. Несмотря на его важную роль в стеке основных данных, вы редко будете напрямую с ним взаимодействовать.

Объект NSManagedObjectContext управляет коллекцией объектов модели, экземпляров класса NSManagedObject . Приложение может иметь несколько контекстов управляемого объекта. Каждый контекст управляемого объекта поддерживается постоянным координатором хранилища.

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

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

Давайте рассмотрим стек основных данных более подробно, посмотрев на пример. Создайте новый проект в Xcode 7, выбрав New> Project … из меню File . Выберите шаблон приложения Single View из списка iOS> Шаблоны приложений слева.

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

Назовите проект Core Data , задайте для языка « Swift» , « Устройства» для iPhone и установите флажок « Использовать базовые данные» . Сообщите Xcode, где вы хотите сохранить файлы проекта, и нажмите « Создать» .

Конфигурация проекта

По умолчанию Apple помещает код, связанный с базовыми данными, в класс делегата приложения, в нашем примере это класс AppDelegate . Откройте AppDelegate.swift и давайте начнем исследовать реализацию класса AppDelegate .

В верхней части AppDelegate.swift вы должны увидеть оператор импорта для инфраструктуры основных данных.

1
2
import UIKit
import CoreData

Класс AppDelegate дополнительно содержит четыре ленивых хранимых свойства:

  • applicationDocumentsDirectory типа NSURL
  • managedObjectModel типа NSManagedObjectModel
  • managedObjectContext типа NSManagedObjectContext
  • persistentStoreCoordinator типа NSPersistentStoreCoordinator

Первое свойство, applicationDocumentsDirectory , является не чем иным, как помощником для доступа к каталогу документов приложения. Реализация довольно проста, как вы можете видеть ниже. Класс NSFileManager используется для извлечения местоположения каталога документов приложения.

1
2
3
4
lazy var applicationDocumentsDirectory: NSURL = {
    let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
    return urls[urls.count-1]
}()

Оставшиеся три ленивых хранимых свойства более интересны и напрямую связаны с платформой Core Data. Сначала мы рассмотрим свойство managedObjectContext .

Класс, который вы будете использовать чаще всего, кроме NSManagedObject , при взаимодействии с базовыми данными — NSManagedObjectContext .

1
2
3
4
5
6
lazy var managedObjectContext: NSManagedObjectContext = {
    let coordinator = self.persistentStoreCoordinator
    var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
    managedObjectContext.persistentStoreCoordinator = coordinator
    return managedObjectContext
}()

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

Прежде чем мы возвращаем контекст управляемого объекта, мы устанавливаем свойство объекта persistentStoreCoordinator . Без постоянного координатора хранилища контекст управляемого объекта бесполезен. Это было не слишком сложно. Это было?

Таким образом, контекст управляемого объекта управляет коллекцией объектов модели, экземпляров класса NSManagedObject и сохраняет ссылку на постоянный координатор хранилища. Имейте это в виду, читая остальную часть этой статьи.

Как мы видели недавно, к свойству persistentStoreCoordinator обращаются во время настройки контекста управляемого объекта. Посмотрите на реализацию свойства persistentStoreCoordinator , но не позволяйте ему пугать вас. Это на самом деле не так сложно.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
    let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent(«SingleViewCoreData.sqlite»)
    var failureReason = «There was an error creating or loading the application’s saved data.»
    do {
        try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil)
    } catch {
        var dict = [String: AnyObject]()
        dict[NSLocalizedDescriptionKey] = «Failed to initialize the application’s saved data»
        dict[NSLocalizedFailureReasonErrorKey] = failureReason
 
        dict[NSUnderlyingErrorKey] = error as NSError
        let wrappedError = NSError(domain: «YOUR_ERROR_DOMAIN», code: 9999, userInfo: dict)
        NSLog(«Unresolved error \(wrappedError), \(wrappedError.userInfo)»)
        abort()
    }
     
    return coordinator
}()

Вы почти всегда захотите сохранить граф объектов Core Data на диске, и реализация Apple свойства persistentStoreCoordinator использует базу данных SQLite для достижения этой цели. Это распространенный сценарий для приложений Core Data.

При закрытии свойства persistentStoreCoordinator мы начинаем с создания экземпляра класса NSPersistentStoreCoordinator , передавая модель управляемого объекта в качестве аргумента. Мы рассмотрим свойство managedObjectModel .

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

Мы начнем с указания местоположения магазина на диске, используя свойство applicationDocumentsDirectory мы видели ранее. Результат, объект NSURL , передается в метод addPersistentStoreWithType(_:configuration:URL:options:) класса NSPersistentStoreCoordinator . Как указывает имя метода, метод добавляет постоянное хранилище в координатор постоянного хранилища. Метод принимает четыре аргумента.

Сначала мы указываем тип хранилища, NSSQLiteStoreType в этом примере. Базовые данные также поддерживают двоичные хранилища ( NSBinaryStoreType ) и хранилище в памяти ( NSInMemoryStoreType ).

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

Четвертый аргумент — это словарь опций, который позволяет нам изменить поведение постоянного хранилища. Мы вернемся к этому аспекту позже в этой серии и пока перейдем к nil .

Поскольку addPersistentStoreWithType(_:configuration:URL:options:) — это метод выброса, мы заключаем вызов метода в оператор do-catch . Если не появляется никаких ошибок, этот метод возвращает объект NSPersistentStore . Мы не храним ссылку на постоянное хранилище, потому что нам не нужно взаимодействовать с ним после его добавления в координатор постоянного хранилища.

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

В предложении catch мы записываем любые ошибки в консоль и вызываем abort . Вы никогда не должны вызывать abort в производственной среде, потому что это приводит к сбою приложения. Мы будем внедрять менее агрессивное решение позже в этой серии.

Третий и последний кусок головоломки — модель управляемого объекта. Давайте посмотрим на реализацию свойства managedObjectModel .

1
2
3
4
lazy var managedObjectModel: NSManagedObjectModel = {
    let modelURL = NSBundle.mainBundle().URLForResource(«Core_Data», withExtension: «momd»)!
    return NSManagedObjectModel(contentsOfURL: modelURL)!
}()

Реализация очень проста. Мы сохраняем местоположение модели приложения в modelURL и используем его во время создания модели управляемого объекта.

Поскольку инициализатор, init(contentsOfURL:) , возвращает необязательный параметр, мы принудительно распаковываем его перед возвратом результата. Разве это не опасно? Да и нет. Не рекомендуется принудительно разворачивать опции. Однако неудачная инициализация модели управляемых объектов означает, что приложение не может найти модель данных в комплекте приложения. Если это произойдет, значит, что-то пошло не так, что находится вне контроля приложения.

В этот момент вы, вероятно, задаетесь вопросом, на что указывает эта модель, на modelURL указывает modelURL и каков файл с расширением .momd . Чтобы ответить на эти вопросы, нам нужно выяснить, что еще Xcode создал для нас во время настройки проекта.

В навигаторе проекта слева вы должны увидеть файл с именем Core_Data.xcdatamodeld . Это модель данных приложения, которая скомпилирована в файл .momd . Это тот файл .momd, который модель управляемых объектов использует для создания модели данных приложения.

Основная модель данных

Можно иметь несколько файлов модели данных. Класс NSManagedObjectModel прекрасно способен объединять несколько моделей данных в одну, что является одной из наиболее мощных и расширенных функций Core Data.

Базовая структура данных также поддерживает управление версиями модели данных и миграцию. Это гарантирует, что данные, хранящиеся в постоянном хранилище (ях), не будут повреждены. Мы расскажем о версиях и миграциях позже в этой серии.

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

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

Основной стек данных

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

Координатор постоянного хранилища знает модель данных, схему графа объектов, если хотите, через объект NSManagedObjectModel . Модель управляемого объекта создает модель данных приложения из одного или нескольких файлов .momd , двоичных представлений модели данных.

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

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

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

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