Статьи

Управление информацией с CoreData

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

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

Создание схемы

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

Схема — это совокупность «сущностей» (часто называемых «таблицами» в мире веб-разработки). Каждый объект имеет атрибуты, которые его описывают, и может иметь отношения с другими объектами. Например, сущность «стул» может иметь четыре связанных сущности, называемых «ножками». Вы можете получить доступ к определенным chair.legs сущности кафедры, используя метод доступа, например, так: chair.legs (который предположительно вернет массив).

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

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

Orny Core Data Рисунок 1

фигура 1

Нажмите Orny.xcdatamodeld (это файл определения модели данных).

Вы должны увидеть один из следующих двух экранов:

Orny Core Data Рисунок 1

фигура 1

Orny Core Data Рисунок 3

Рисунок 3

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

Чтобы добавить объект, нажмите кнопку «Добавить объект».

Orny Core Data Рисунок 4

Рисунок 4

Эта кнопка иногда может быть помечена как «Добавить запрос на выборку» или «Добавить конфигурацию» в зависимости от того, что вы добавили ранее — в этом случае, сделайте длинный щелчок по кнопке, и вы увидите опцию «Добавить объект»

Создайте сущность с именем Species.

Orny Core Data Рисунок 5

Рисунок 5

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

Orny Core Data Рисунок 6

Рисунок 6

Нам нужно указать типы этих атрибутов — все они должны быть строками. Рядом с каждым атрибутом выберите раскрывающийся список для типа и измените их соответствующим образом.

Orny Core Data Рисунок 7

Рисунок 7

Добавление классов управляемых моделей

Теперь мы создали нашу схему, но нам нужно создать в коде представления сущностей, которыми мы управляем. iOS называет эти «Модели» (как в «Модель, Вид, Контроллер»). Мы запрашиваем подмножества данных в базе данных, используя запросы выборки, которые будут возвращать массивы моделей. Каждый экземпляр класса модели представляет строку в базе данных (или, по крайней мере, делает это после того, как мы сохранили ее в базе данных).

Модели, которые CoreData предоставляет нам, обычно являются «управляемыми» — если мы внесем в них изменения, а затем скажем соответствующему контроллеру постоянного хранилища выполнить «сохранение», он сохранит все изменения, которые мы внесли в любой из наших экземпляров модели. (По сути, это шаблон Active Record , который имеет свои сильные и слабые стороны, но довольно хорошо понят.)

Если у вас его еще нет, создайте папку «Модели» в своем проекте (щелкните правой кнопкой мыши «Orny» в файловом браузере и выберите «Новая группа».)

Щелкните правой кнопкой мыши новую группу моделей и выберите «Новый файл».

Выберите «Основные данные» и «Подкласс NSManagedObject».

Orny Core Data Рисунок 8

Рисунок 8

Нажмите «Далее» и выберите модель данных «Орни».

Orny Core Data Рисунок 9

Рисунок 9

Нажмите «Далее», выберите «Виды»

Orny Core Data Рисунок 10

Рисунок 10

Назовите файл Species, и все готово. Это создает класс модели — Species.m , заголовок которого выглядит следующим образом:

 @interface Species : NSManagedObject { @private } @property (nonatomic, retain) NSString * name; @property (nonatomic, retain) NSString * text_description; @property (nonatomic, retain) NSString * filename; @end 

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

Запрос и получение результатов

Наш appDelegate уже имеет большинство методов, которые нам нужны для доступа к нашей базе данных. Взгляните на OrnyAppDelegate.m и, в частности, методы managedObjectContext , managedObjectModel , persistentStoreCoordinator и saveContext .

persistentStoreCoordinator создает экземпляр нашей базы данных из файла .sqlite и соотносит содержимое этого файла с нашей моделью управляемых объектов с помощью managedObjectModel . Мы изменим это позже, чтобы скопировать в нашу базу данных, если ни один не существует при запуске (см. Ниже).

managedObjectContext получает контекст управляемого объекта (что удивительно удивительно!), который является корреляцией файла базы данных, модели управляемого объекта и контроллера, который используется для получения результатов и сохранения данных. NSManagedObjectContext — это класс, с которым мы будем взаимодействовать больше всего.

Наши цели здесь — хранить данные о видах птиц в базе данных и отображать эти данные пользователю. Отображение данных происходит в нашем BirdListViewController , поэтому давайте изменим метод loadData следующим образом:

 -(void)loadBirdData { // The context is, roughly, the "database schema" NSManagedObjectContext *context = [(OrnyAppDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext]; // A request is like an SQL select statement; we're retrieving some set of objects NSFetchRequest *request = [[NSFetchRequest alloc] init]; // An entity description is used to specify which entit(y|ies) we want to pull from the context NSEntityDescription *description = [NSEntityDescription entityForName:@"Species" inManagedObjectContext:context]; [request setEntity:description]; // A sort descriptor lets us order the results NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO]; [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]]; // A fetchedResultsController handles the fetching of our data NSFetchedResultsController *fetchController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil]; fetchController.delegate = self; NSError *error = nil; if(![fetchController performFetch:&error]) { // TODO: Handle error abort(); } birds = fetchController.fetchedObjects; } 

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

Ooh. Мы изменили формат наших структур данных. Ранее мы вызывали [birds objectAtIndex:someIndex] чтобы получить определенную «строку» в нашем наборе данных. Затем мы вызываем [species objectForKey:@"name"] или тому подобное, чтобы получить атрибут этого вида.

Нам нужно переписать еще немного нашего BirdListViewController , в частности tableView:tableView cellForRowAtIndexPath:indexPath :

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *newCell; if((newCell = [tableView dequeueReusableCellWithIdentifier:@"birdList"]) == nil) { newCell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"birdList"] autorelease]; } //NSDictionary *thisBird = [birds objectAtIndex:[indexPath row]]; Species *thisBird = [birds objectAtIndex:[indexPath row]]; UILabel *newCellLabel = [newCell textLabel]; //[newCellLabel setText:[thisBird objectForKey:@"name"]]; [newCellLabel setText:thisBird.name]; return newCell; } 

Нам также нужно будет переписать tableView:tableView didSelectRowAtIndexPath:indexPath :

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:NO]; //NSDictionary *thisBird = [birds objectAtIndex:[indexPath row]]; Species *bird = [birds objectAtIndex:[indexPath row]]; BirdListDetailViewController *detail = [[BirdListDetailViewController alloc] initWithNibName:@"BirdListDetailViewController" bundle:[NSBundle mainBundle]]; //detail.filename = [thisBird objectForKey:@"image"]; detail.filename = bird.filename; [[self navigationController] pushViewController:detail animated:YES]; [detail release]; } 

Я оставил наш предыдущий код в основном на месте, но закомментировал, чтобы вы могли увидеть разницу (но вы можете увидеть гораздо больше, если загляните в репозиторий BuildMobile GitHub для Orny . Чтение исходного кода и коммитов — отличный способ учить!)

Если вы запустите приложение сейчас, оно должно работать — но вы не увидите никаких видов в списке. Это потому, что наша база данных пуста, и мы должны предварительно заполнить ее …

Предварительно заполненные данные

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

  1. Создание файла .sqlite вручную
  2. Заставить ваше приложение сохранить копию пустой базы данных, а затем изменить ее, чтобы она стала вашей базой данных по умолчанию
  3. Заполнение базы данных вашего приложения при первом запуске (что может быть проблематично для пользователей, которые могут не подключаться к Интернету)

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

Этот подход хорошо работает для простого приложения, но имейте в виду, что если вы впоследствии захотите добавить данные в базу данных при выпуске новой версии вашего приложения, вам придется сделать что-то особенное, чтобы объединить ваши новые данные с пользовательская база данных. Одно из решений — использовать две базы данных: одну для хранения данных вашего приложения и одну для хранения вашего пользователя. Дальнейшее обсуждение выходит за рамки этого урока, однако — к сути!

Давайте заставим приложение сохранить нам пустую базу данных. Для этого мы loadBirdData метод loadBirdData — добавим следующее в конец этой функции:

 // Adding a saveContext call, to generate an empty sqlite db [(OrnyAppDelegate*)[[UIApplication sharedApplication] delegate] saveContext]; 

Мы говорим AppDelegate, чтобы сохранить наш managedObjectContext. Neato. Запустите приложение — многого не случится, но теперь мы сможем найти базу данных .sqlite в…

/ Пользователь / _username / Библиотека / Поддержка приложений / iPhone Simulator / _sdk версия / Приложения / _some magic string_ / Documents / Orny.sqlite

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

Получив этот файл .sqlite, перетащите его в группу вспомогательных файлов своего приложения и переименуйте в Orny_default.sqlite .

Orny Core Data Рисунок 11

Рисунок 11

Теперь мы хотим изменить файл базы данных. В настоящее время я использую SQLite Database Browser , но любой инструмент, который может читать и изменять файлы базы данных .sqlite, подойдет.

Установив это приложение, вы сможете изменить файл, щелкнув правой кнопкой мыши и выбрав «Открыть с помощью внешнего редактора».

Orny Core Data Рисунок 12

Рисунок 12

В браузере SQLite перейдите на вкладку «Просмотр данных» и выберите «ZSPECIES» из раскрывающегося списка.

Orny Core Data Рисунок 13

Рисунок 13

Вставьте строку, как показано на рисунке 13.

После этого нам также нужно изменить таблицу PRIMARYKEY. Именно здесь CoreData хранит записи об объектах, которыми он управляет для этой базы данных, имени объекта и количестве записей в базе данных. Измените столбец Z_MAX, как показано на рисунке 14.

Orny Core Data Рисунок 14

Рисунок 14

Нажмите значок сохранения, и мы закончили изменять базу данных.

Вы также можете использовать скрипт для создания базы данных по умолчанию — см. Статью Рэя Вендерлиха о том, как это сделать с помощью Python.

Копирование в нашей базе данных по умолчанию

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

 - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (__persistentStoreCoordinator != nil) { return __persistentStoreCoordinator; } //[[self applicationDocumentsDirectory] stringByAppendingPathComponent:]; NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Orny.sqlite"]; NSFileManager *fileManager = [NSFileManager defaultManager]; if(![fileManager fileExistsAtPath:[storeURL path]]) { NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"Orny_default" ofType:@"sqlite"]; if (defaultStorePath) { NSLog(@"COPYING"); NSLog(@"%@", [storeURL relativeString]); NSLog(@"%@", defaultStorePath); NSError *error; if(![fileManager copyItemAtPath:defaultStorePath toPath:[storeURL path] error:&error]) { NSLog(@"FILE COPY ERROR: %@", [error localizedDescription]); } } } NSError *error = nil; __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; ... 

Я сократил список выше для краткости, но вы можете видеть строки, в которые мы копируем.

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

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

Обработка ошибок

Методы CoreData по умолчанию в нашем OrnyAppDelegate активно комментируются Apple и реализуют для нас очень ограниченную обработку ошибок. Если вы когда-нибудь захотите опубликовать приложение в магазине приложений, вы должны прочитать эти комментарии и элегантно обработать ошибки. То, как вы справляетесь с ошибкой, зависит от вас — вы можете заново создать базу данных или просто показать пользователю сообщение с просьбой перезапустить приложение, но вы обязательно должны что-то сделать.

Я оставлю это приключение для вас!

Вывод

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

Серия «Орни»

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