Статьи

Основные данные с нуля: стек основных данных

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

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

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

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

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

То, что я расскажу в этой серии о Core Data, применимо к iOS 6+ и OS X 10.8+, но основное внимание будет уделено iOS. В этой серии я буду работать с Xcode 5 и iOS 7 SDK.

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

В этой статье я помогу вам ознакомиться с так называемым стеком базовых данных . Как только вы поймете ключевых игроков стека базовых данных, инфраструктура станет менее сложной, и вы даже начнете наслаждаться и ценить хорошо разработанный 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 . Вполне возможно иметь несколько контекстов управляемых объектов. Каждый контекст управляемого объекта поддерживается постоянным координатором хранилища.

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

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

Давайте рассмотрим стек Core Data более подробно, посмотрев на шаблон Apple Xcode для Core Data. Создайте новый проект в Xcode 5, выбрав New> Project … из меню File . Выберите Пустой шаблон приложения из списка шаблонов приложений iOS слева.

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
#import <UIKit/UIKit.h>
 
@interface TSPAppDelegate : UIResponder <UIApplicationDelegate>
 
@property (strong, nonatomic) UIWindow *window;
 
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
 
— (void)saveContext;
— (NSURL *)applicationDocumentsDirectory;
 
@end

Как видите, у делегата приложения есть свойство для каждого компонента стека saveContext данных, а также два saveContext метода: saveContext и applicationDocumentsDirectory .

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

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

1
2
3
4
5
6
7
#import «TSPAppDelegate.h»
 
@implementation TSPAppDelegate
 
@synthesize managedObjectContext = _managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;

Поскольку свойства в интерфейсе класса TSPAppDelegate объявлены только для чтения, методы установки не создаются. Первая директива @synthesize указывает компилятору связать _managedObjectContext экземпляра _managedObjectContext со свойством managedObjectContext мы объявили в интерфейсе класса. Это обычный шаблон для ленивой загрузки объектов.

Вы можете достичь того же результата, используя расширение частного класса в файле реализации класса. Взгляните на следующий фрагмент кода. Директивы @synthesize не нужны, если вы используете расширение частного класса.

1
2
3
4
5
6
7
8
9
#import «TSPAppDelegate.h»
 
@interface TSPAppDelegate ()
 
@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
 
@end

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

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

На практике это означает, что реализация класса TSPAppDelegate выглядит аналогично тому, что вы ожидаете от класса делегата приложения, за исключением методов saveContext и applicationDocumentsDirectory , а также методов-получателей managedObjectContext , managedObjectModel и persistentStoreCoordinator . Именно в этих методах получения происходит волшебство. Это одна из красот Core Data, настройка очень проста, и взаимодействие с Core Data так же просто.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
— (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }
     
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

Первые три строки его реализации типичны для геттера, который лениво загружает переменную экземпляра. Если объект NSManagedObjectContext не равен nil , он возвращает объект. Интересным является фактическое создание объекта NSManagedObjectContext .

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

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

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

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
— (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
     
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@»Core_Data.sqlite»];
     
    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        /*
         Replace this implementation with code to handle the error appropriately.
          
         abort() causes the application to generate a crash log and terminate.
          
         Typical reasons for an error here include:
         * The persistent store is not accessible;
         * The schema for the persistent store is incompatible with current managed object model.
         Check the error message to determine what the actual problem was.
          
          
         If the persistent store is not accessible, there is typically something wrong with the file path.
          
         If you encounter schema incompatibility errors during development, you can reduce their frequency by:
         * Simply deleting the existing store:
         [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
          
         * Performing automatic lightweight migration by passing the following dictionary as the options parameter:
         @{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES}
          
         Lightweight migration will only work for a limited set of schema changes;
          
         */
        NSLog(@»Unresolved error %@, %@», error, [error userInfo]);
        abort();
    }
     
    return _persistentStoreCoordinator;
}

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

Когда мы создаем постоянный координатор хранилища в persistentStoreCoordinator , мы указываем местоположение хранилища на диске. Мы начнем с создания объекта NSURL который указывает на это местоположение в песочнице приложения. Мы вызываем applicationDocumentsDirectory , вспомогательный метод, который возвращает местоположение, NSURL объект каталога Documents в изолированной программной NSURL приложения. Мы добавляем Core_Data.sqlite к местоположению и сохраняем его в storeURL для дальнейшего использования.

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

1
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@»Core_Data.sqlite»];

Как я уже упоминал, расширение .sqlite намекает на то, что хранилище на диске — это база данных SQLite. Несмотря на то, что Core Data поддерживает несколько типов хранилищ, SQLite является наиболее часто используемым типом хранилищ из-за его скорости и надежности.

1
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

На следующем шаге мы создаем экземпляр постоянного координатора хранилища, вызывая initWithManagedObjectModel: и передавая экземпляр NSManagedObjectModel . Мы получаем ссылку на модель управляемого объекта, вызывая метод managedObjectModel , который мы рассмотрим далее.

Теперь у нас есть экземпляр класса NSPersistentStoreCoordinator , но с ним еще не связано ни одного хранилища. Мы добавляем хранилище в координатор постоянного хранилища, вызывая довольно впечатляющий метод addPersistentStoreWithType:configuration:URL:options:error:

1
2
3
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
     
}

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

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

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

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

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

В данный момент abort вызывается, когда addPersistentStoreWithType:configuration:URL:options:error: возвращает nil . Как поясняется в комментариях к оператору if , вы никогда не должны вызывать abort в производственной среде, потому что это приводит к сбою приложения. Мы исправим это позже в этой серии.

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

1
2
3
4
5
6
7
8
9
— (NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@»Core_Data» withExtension:@»momd»];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

Реализация очень проста. Мы сохраняем местоположение модели приложения в modelURL и передаем modelURL в initWithContentsOfURL: для создания экземпляра класса NSManagedObjectModel .

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

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

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

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

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

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

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

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

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

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

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

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

В то же время, если вы хотите стать разработчиком для iOS, обязательно ознакомьтесь с ассортиментом шаблонов приложений для iOS на Envato Market. С более чем 1000 предметов на выбор, вы обязательно найдете что-то, что поможет вам в вашем следующем проекте.