Core Data — это инфраструктура, которую Apple предоставляет разработчикам, которая описывается как «управляемая схемой структура графов объектов и постоянство». Что это на самом деле означает? Инфраструктура управляет тем, где хранятся данные, как они хранятся, кэширование данных и управление памятью. Он был портирован на iPhone из Mac OS X с выпуском 3.0 iPhone SDK.
В этом руководстве я собираюсь рассказать вам о процессе создания проекта с использованием Core Data и покажу, как его использовать с простым UITableViewController. Это будет простая однотабличная база данных, в которой будут храниться данные о времени прохождения круга и отображаться время и их различия в UITableView.
Зачем использовать основные данные?
Прежде чем мы начнем, важно понять, почему вы можете использовать Core Data с SQLite для хранения списков свойств, пользовательского формата XML или прямого доступа к базе данных SQLite. Базовый API данных позволяет разработчикам создавать и использовать реляционную базу данных, выполнять проверку записей и выполнять запросы, используя условия без SQL. По сути, он позволяет вам взаимодействовать с SQLite в Objective-C и не беспокоиться о соединениях или управлении схемой базы данных, и большинство этих функций знакомы людям, которые использовали технологии объектно-реляционного отображения (ORM), подобные тем, которые реализованы в Ruby. на Rails, CakePHP, LINQ или других библиотеках и средах, которые абстрагируют доступ к базе данных. Основным преимуществом этого подхода является то, что он устраняет время разработки, в противном случае необходимо писать сложные запросы SQL и вручную обрабатывать SQL и вывод этих операций.
Создание нового проекта iPhone с Core Data
Пример приложения, которое мы будем создавать сегодня, — простой таймер круга. Это создаст новую запись в Базовом хранилище данных для каждого круга, который мы делаем. UITableView покажет разницу между текущим и последним кругами.
Для начала мы откроем XCode и создадим новый проект. Назовите его как хотите, я назвал его «LapTimer». Мы будем создавать «оконное приложение» из шаблона «Новый проект». Убедитесь, что установлен флажок «Использовать основные данные для хранения».
Проект должен быть знаком с тем, что вы, возможно, видели раньше, если ранее разрабатывали приложения для iPhone.
Настройка базовой структуры данных
Не так много настроек, так как опция «Использовать базовые данные для хранения» в шаблоне «Приложение на основе окна» автоматически установила для нас некоторые важные переменные и созданные файлы в проекте.
В файле LapTimer.xcdatamodel мы определим схему для нашей базы данных SQLite. Базовая структура данных также была добавлена в проект для включения файлов для доступа к API. Другие изменения вносятся в файлы приложения по умолчанию. В частности, файлы делегатов приложения имеют методы для настройки основного хранилища данных в нашем приложении и ссылки на него в дочерних UIViewControllers.
На данный момент нас интересует файл LapTimer.xcdatamodel в разделе «Ресурсы». Этот файл дает вам визуальную карту вашей схемы, показывающую сущности и атрибуты.
В Базовых данных используются некоторые разные термины и фразы, которые могут быть слабо связаны с обычными именами баз данных, но они не идентичны.
«Сущность», также известная как «управляемый объект», похожа на таблицу. Это определение объекта, который будет содержать коллекцию данных. Объект сущности содержит «атрибуты». Они могут быть слабо связаны со столбцами, но эти атрибуты не ограничиваются только хранением данных. Атрибуты могут определять отношения между двумя объектами, динамически извлекаемое свойство или определять свойство для хранения данных.
На диаграмме выше вы можете почувствовать, насколько гибкими являются объекты, полученные из сущностей базовых данных. Для этого примера приложения нам понадобится действительно простая сущность. Мы назовем объект «Событие», который будет хранить записи наших кругов.
Чтобы создать эту сущность, мы нажмем кнопку [+] в первом (слева) верхнем столбце. Это создаст новый объект в списке и визуально на карте схемы под столбцами.
Это хороший визуальный редактор для вашей модели. Core Data действительно делает тяжелую работу, когда дело доходит до «M» (модель) части MVC. Визуальный редактор показывает отношения, свойства и сущности хранилища, пока схема динамически создается и управляется всеми для вас. Это похоже на Интерфейсный Разработчик, поскольку он заботится о распределении, управлении и размещении объектов для вас в UIView без единой строки кода.
С новой сущностью Event мы хотим создать «свойство». Поскольку это свойство будет хранить данные, мы установим его как «атрибут». Таким образом, этот новый атрибут будет просто хранить текущую дату, когда была создана запись. В нашем примере приложения мы будем использовать это для ссылки на наше время круга.
В следующем столбце справа мы определяем наши свойства (убедитесь, что выбран объект Event). Итак, создайте новое свойство, используя кнопку [+] в столбце, выберите «Добавить атрибут».
Мы будем называть атрибут «timeStamp» и устанавливать его тип «Date». В раскрывающемся списке выбора типов есть типы данных столбцов, аналогичные тем, которые доступны в системах реляционных баз данных, таких как PostgreSQL или MySQL, включая типы данных, такие как целые числа, числа с плавающей запятой, строки, логические значения, даты и двоичные данные (большие двоичные объекты).
Для этого атрибута нам не нужны никакие другие параметры, выбранные или измененные.
Вот и все для файла xcdatamodel, и мы можем перейти к его интеграции в наше приложение. Сейчас самое время сохранить вашу работу.
Создание наших файлов моделей
Если вы использовали инфраструктуру MVC, которая имеет определения модели базы данных, определяющие структуру или поведение таблицы, то это будет знакомая задача.
Нам нужно начать с создания определения сущности. Мы делаем это путем создания класса NSManagedObject объекта и определяем переменные, которые есть в хранилище.
Это простой процесс. Выберите объект «Событие» в файле LapTimer.xcdatamodel и выберите «Файл»> «Новый файл». Вы увидите, что в разделе «Класс касания какао» появился новый шаблон файла, который называется «Класс управляемых объектов».
Выберите шаблон и нажмите «Далее». Следующий экран просто определяет, где мы сохраняем файл и цель, к которой он будет добавлен. По умолчанию все это правильно, поэтому снова нажмите «Далее». На следующем экране вы определяете, для каких сущностей вы хотите создать классы NSManagedObject. Если он не выбран, выберите «Событие» и убедитесь, что установлены флажки «Сгенерировать средства доступа» и «Сгенерировать свойства Obj-C 2.0», в настоящее время нам не нужны проверки. Нажмите Finish.
Итак, теперь у нас есть 2 новых файла в нашем приложении. Event.m и Event.h. Они определяют класс NSManagedObject для сущности Event, которую мы создали в нашем Базовом хранилище данных. Они определяют поле timeStamp, поэтому, когда мы хотим использовать класс Event, мы можем получить доступ к атрибуту.
Event.h
#import <CoreData / CoreData.h> Событие @interface: NSManagedObject { } @property (nonatomic, retain) NSDate * timeStamp; @конец
Event.m
#import "Event.h" @implementation Event @dynamic timeStamp; @конец
Как и определения моделей в других платформах и языках, вы можете добавлять собственные методы для всех записей в сущности Event. Вы заметите, что атрибут timeStamp был добавлен и назначен как объект NSDate.
Основные данные и KVC
Теперь с настройкой Core Data пришло время поработать над некоторой логикой контроллера в приложении. Мы будем использовать UITableViewController в качестве основного интерфейса приложения, чтобы показывать время прохождения круга, а также записывать новое время.
Итак, мы создадим новый UITableViewController с File> New File. Затем в разделе iPhone выберите «UIViewController subclass» и установите флажок «UITableViewController subclass», но не устанавливайте флажки, связанные с использованием XIB или для ориентации на iPad. Мы не будем использовать XIB для этого контроллера. Вызовите новый файл «TimeTableController» и завершите работу мастера файлов.
В этом контроллере нам понадобятся 2 свойства: ссылка на NSManagedObjectContext и массив для хранения записей для UITableView. Помимо определения этих свойств, мы импортируем файл Event.h, чтобы мы могли использовать класс.
TimeTableController.h
#import <UIKit / UIKit.h> #import "Event.h" @interface TimeTableController: UITableViewController { NSManagedObjectContext * managedObjectContext; NSMutableArray * eventArray; } @property (nonatomic, retain) NSManagedObjectContext * managedObjectContext; @property (nonatomic, retain) NSMutableArray * eventArray; - (недействительно) fetchRecords; - (void) addTime: (id) отправитель; @конец
Что такое NSManagedObjectContext? Он называется «блокнотом» для Core Data в приложении для управления извлечением, обновлением и созданием записей в хранилище. Он также управляет несколькими фундаментальными функциями в Core Data, включая проверки и управление отменой и повторением записей.
Контекст управляемого объекта — это связь между вашим кодом и хранилищем данных. Все операции, которые вы будете выполнять для Базовых данных, будут выполняться в контексте управляемого объекта. Когда запрос выполняется, контекст управляемого объекта будет затем общаться с постоянным координатором хранилища, который отвечает за сопоставление объектов с данными для хранилища данных. Это позволяет базовым данным быть гибкими между различными форматами хранилища данных. Вот схема того, как это выглядит.
Определив заголовочный файл, мы должны распространить выделенные свойства и методы в файле реализации.
TimeTableController.m
#import "TimeTableController.h" @implementation TimeTableController @synthesize managedObjectContext, eventArray; // ... // ... код комментария по умолчанию из шаблона файла // ... - (void) viewDidLoad { [super viewDidLoad]; self.title = @ "Lap Times"; UIBarButtonItem * addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemAdd target: self action: @selector (addTime :)]; self.navigationItem.rightBarButtonItem = addButton; [addButton release]; [self fetchRecords]; } - (void) addTime: (id) sender { Событие * событие = (Событие *) [NSEntityDescription insertNewObjectForEntityForName: @ «Событие» inManagedObjectContext: managedObjectContext]; [событие setTimeStamp: [дата NSDate]]; Ошибка NSError *; if (! [managedObjectContext save: & error]) { // Это серьезная ошибка при записи записи // не удалось сохранить. Посоветуйте пользователю // попробуйте снова или перезапустите приложение. } [eventArray insertObject: event atIndex: 0]; [self.tableView reloadData]; } - (void) fetchRecords { // Определяем нашу таблицу / сущность для использования NSEntityDescription * entity = [NSEntityDescription entityForName: @ "Событие" inManagedObjectContext: managedObjectContext]; // Установить запрос на выборку NSFetchRequest * request = [[NSFetchRequest alloc] init]; [request setEntity: entity]; // Определим, как мы будем сортировать записи NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey: @ "timeStamp" ascending: NO]; NSArray * sortDescriptors = [NSArray arrayWithObject: sortDescriptor]; [request setSortDescriptors: sortDescriptors]; [sortDescriptor release]; // Извлекаем записи и обрабатываем ошибку Ошибка NSError *; NSMutableArray * mutableFetchResults = [[managedObjectContext executeFetchRequest: ошибка запроса: & ошибка] mutableCopy]; if (! mutableFetchResults) { // Обрабатываем ошибку. // Это серьезная ошибка, которая должна посоветовать пользователю перезапустить приложение } // Сохраняем полученные данные в массив [self setEventArray: mutableFetchResults]; [mutableFetchResults release]; [запрос релиза]; } // ... // ... другие комментарии к шаблону и определения методов по умолчанию // ... - (void) dealloc { [managedObjectContext release]; [releaseArray release]; [супер сделка]; } @конец
Это довольно много кода, поэтому давайте пройдемся по каждому методу в отдельности. Есть код от того, когда вы создаете файл из шаблона, закомментированные методы, такие как viewDidUnload, которые я только что исключил из приведенного выше.
viewDidLoad
Мы начинаем с обычного звонка в суперкласс. Затем мы определяем заголовок UINavigationBar. После этого нам нужно определить кнопку, которую мы будем использовать для добавления записи в хранилище Core Data. Когда кнопка нажата, мы говорим ей вызвать селектор addTime: который затем будет взаимодействовать с Core Data. После необходимых выпусков мы переходим к вызову функции fetchRecords, что объясняется ниже.
AddTime: (ID) отправителя
Это срабатывает при нажатии UIBarButtonItem в верхнем правом углу UINavigationBar. Нам нужно создать новую запись о событии с текущей NSDate и сохранить ее в базе данных.
Мы создаем новую запись события, которая называется NSEntityDescription. Это ваша строка в базе данных для новой записи. Для этого мы определяем имя сущности, к которой принадлежит запись, и предоставляем NSManagedObjectContext. Текущая дата затем устанавливается на атрибут timeStamp.
Затем выполняется операция сохранения, но существует условие для обработки ошибки в случае сбоя вставки. Обычно вы бы выдавали UIAlertView, говоря, что запись не была создана, и, возможно, предлагали бы пользователю повторить попытку или закрыть и снова открыть приложение.
Запись должна быть добавлена в массив, из которого поступает UITableView. Затем необходимо указать UITableView для перезагрузки данных. Вы можете сделать это более графически с помощью анимации, но ради учебника давайте сделаем это проще.
fetchData
Этот метод получит данные из хранилища и добавит их в массив, который есть в контроллере. Для получения дополнительной информации о получении записей давайте внимательно рассмотрим NSFetchRequest.
Извлечение данных из хранилища данных
Core Data использует другой подход для извлечения данных из своей базы данных. Это хранилище данных NoSQL, означающее, что все условия запроса основаны на методах. Это замечательно, поскольку базовое хранилище, которое представляет собой SQLite, может быть изменено на любую другую технологию баз данных, и единственное, что нужно изменить, это подключение и драйверы для базы данных.
Итак, для создания запроса мы создаем объект NSFetchRequest. Это базовый объект, против которого будут установлены условия запроса. Мы можем определить условия для сопоставления конкретного свойства на основе порядка упорядочения записей. Вы можете узнать больше о Базовых данных и их условиях для NSFetchRequests в их документации.
Создать новый запрос NSFetch просто. Вам просто нужно определить сущность, от которой вы хотите получить записи, и NSManagedObjectContext.
NSEntityDescription * entity = [NSEntityDescription entityForName: @ "Событие" inManagedObjectContext: managedObjectContext]; NSFetchRequest * request = [[NSFetchRequest alloc] init]; [request setEntity: entity];
Сущность определяется с использованием объекта NSEntityDescription, для которого требуется имя сущности и NSManagedObjectContext. Запрос выборки тогда создан, передавая описание объекта. Это будет соответствовать первой части инструкции SQL:
SELECT * FROM `events`
В нашем примере приложения мы сортируем данные по timeStamp по убыванию. Для этого мы используем NSSortDescriptor.
NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey: @ "timeStamp" ascending: NO]; NSArray * sortDescriptors = [NSArray arrayWithObject: sortDescriptor]; [request setSortDescriptors: sortDescriptors]; [sortDescriptor release];
NSSortDescriptor создан, и мы определяем атрибут, который мы хотим отсортировать, и убираем ли он по возрастанию, в этом случае мы хотим, чтобы он спускался, поэтому для него установлено значение NO. Запрос на выборку может принимать много дескрипторов сортировки, поэтому он принимает массив при установке дескрипторов сортировки. Поскольку нам нужен только один, нам просто нужно создать массив с одним объектом в нем. Мы устанавливаем массив дескрипторов для запроса на выборку, и это все.
Для определения условия соответствия содержимому записи в игру вступает класс NSPredicate. Это позволяет запросу выборки соответствовать или определять диапазон, которому должно соответствовать содержимое записи. Это эквивалент ваших равных, больше и меньше совпадений в SQL. Он имеет больше, чем ваши основные функции SQL, которые вы можете увидеть здесь.
Установка предиката может быть очень простой.
NSPredicate * предикат = [NSPredicate ultimateateWithFormat: @ "(lastName like% @) AND (birthday>% @)", lastNameSearchString, birthdaySearchDate];
Использование NSPredicate ForexateWithFormat: это простой и знакомый метод, который позволяет определять условия запроса. Для подробного объяснения NSP-предикатов в документации Apple есть несколько замечательных руководств.
Когда вы определили условия в своем запросе на выборку, вы можете выполнить его.
NSMutableArray * mutableFetchResults = [[managedObjectContext executeFetchRequest: ошибка запроса: & ошибка] mutableCopy];
Это вернет массив объектов-сущностей, NSManagedObjects, для использования в ваших данных.
Заполнение UITableView из базовых данных
Получив данные из Core Data и сохранив их в eventArray, мы теперь можем вывести эти записи в UITableView.
Прежде всего, нужно сообщить таблице, что нам потребуется только 1 раздел и сколько строк нам нужно использовать.
Выдержка из TimeTableController.m
- (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView { возврат 1; } - (NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) section { return [eventArray count]; }
Если вы ранее использовали UITableViewController, следующая функция должна быть прямой.
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath { статическая NSString * CellIdentifier = @ "Cell"; статический NSDateFormatter * dateFormatter = nil; if (dateFormatter == nil) { dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat: @ "h: mm.ss a"]; } UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleValue1 reuseIdentifier: CellIdentifier] autorelease]; } Событие * event = [eventArray objectAtIndex: [indexPath row]]; Событие * previousEvent = nil; if ([eventArray count]> ([indexPath row] + 1)) { previousEvent = [eventArray objectAtIndex: ([indexPath row] + 1)]; } [cell.textLabel setText: [dateFormatter stringFromDate: [event timeStamp]]]; if (previousEvent) { NSTimeInterval timeDifference = [[event timeStamp] timeIntervalSinceDate: [previousEvent timeStamp]]; [cell.detailTextLabel setText: [NSString stringWithFormat: @ "+%. 02f sec", timeDifference]]; } еще { [cell.detailTextLabel setText: @ "---"]; } возвратная ячейка; }
В ячейке будут отображаться 2 значения из стиля UITableViewCellStyleValue1. Слева будет время круга, а справа будет разница в секундах от предыдущей записи.
Поскольку этот метод повторяется, мы должны быть особенно внимательны с нагрузкой, которую оно может поставить под устройство, если оно не управляется правильно. По этой причине NSDatFormatter хранится как статическая переменная, поэтому его можно использовать повторно в каждой итерации, не выделяя и не освобождая его каждый раз.
Ленивая Загрузка
Ленивая загрузка — это метод, при котором вы максимально задерживаете запрос или выделение свойства. Это помогает сохранить память и во время итеративных функций это имеет первостепенное значение. Знание того, когда и как распределять данные, крайне важно для поддержания скорости мобильного приложения. Распределение объекта также важно, чем раньше, тем лучше.
cellForRowAtIndexPath: является итеративным методом, и любые данные, обрабатываемые или выделяемые в этом методе, особенно необходимо сохранять как минимум. Этот метод запускается всякий раз, когда в поле зрения появляется ячейка, поэтому, когда пользователь быстро прокручивает этот конкретный метод, в зависимости от размера набора записей, его можно вызывать очень часто подряд.
Следующая задача — получить объект события, связанный со строкой таблицы, которую необходимо отобразить. Поскольку нам нужно получить предыдущую запись для сравнения времени, существует простая проверка, чтобы увидеть, есть ли предыдущая запись, и сохранить ее в previousEvent. Если предыдущее событие существует, то мы вычисляем разделение, используя [NSDate timeIntervalSinceDate: (NSDate)]. Затем textLabel и detailsTextLabel устанавливаются с помощью вычисленных нами значений.
Завершение приложения
С установкой UITableViewController и источником данных таблицы, работающим с хранилищем базовых данных, все, что нужно, это загрузить контроллер при запуске приложения.
В контроллере приложения необходимо определить свойство UINavigationController. Затем метод applicationDidFinishLaunching просто должен выделить контроллер, и все готово.
LapTimerAppDelegate.h
@interface LapTimerAppDelegate: NSObject <UIApplicationDelegate> { NSManagedObjectModel * managedObjectModel; NSManagedObjectContext * managedObjectContext; NSPersistentStoreCoordinator * persistentStoreCoordinator; UIWindow * window; UINavigationController * navigationController; } @property (nonatomic, retain, readonly) NSManagedObjectModel * managedObjectModel; @property (nonatomic, retain, readonly) NSManagedObjectContext * managedObjectContext; @property (nonatomic, retain, readonly) NSPersistentStoreCoordinator * persistentStoreCoordinator; @property (nonatomic, retain) IBOutlet UIWindow * window; @property (nonatomic, retain) UINavigationController * navigationController; - (NSString *) applicationDocumentsDirectory; @конец
Выдержка из LapTimerAppDelegate.m
#import "LapTimerAppDelegate.h" #import "TimeTableController.h" @implementation LapTimerAppDelegate окно @synthesize, navigationController; - (void) applicationDidFinishLaunching: (UIApplication *) application { TimeTableController * tableController = [[TimeTableController alloc] initWithStyle: UITableViewStylePlain]; tableController.managedObjectContext = [self managedObjectContext]; self.navigationController = [[UINavigationController alloc] initWithRootViewController: tableController]; [релиз tableController]; [окно addSubview: [представление self.navigationController]]; [window makeKeyAndVisible]; } // ... // ... другие методы шаблона // ... - (void) dealloc { [managedObjectContext release]; [managedObjectModel release]; [persistentStoreCoordinator release]; [окно выпуска]; [релиз навигационного контроллера]; [супер сделка]; }
Файл TimeTableController.h включается в делегат приложения и затем выделяется с помощью UINavigationController.
Это должно быть так. Создайте приложение, чтобы проверить наличие ошибок. Некоторые примеры кода были только выдержками, ни один код, созданный при создании файла, не был удален, а только заполнен. Если вы столкнетесь с ошибками, которые не можете взломать, вы можете скачать файл проекта, прикрепленный к этому учебному пособию. который вы можете затем скомпилировать и сравнить.
Запустите приложение. Вы увидите навигационный контроллер и кнопку добавления. Нажмите кнопку добавления, и вы получите новое время в таблице.
резюмировать
В этом руководстве мы создали пример приложения для хранения простых данных в хранилище Core Data. Приложение прошло процедуру начальной настройки при создании приложения с Core Data, определении структуры данных и извлечении записей из хранилища данных.
Надеемся, что вы ознакомились с Core Data и увидели, как легко им пользоваться и как они могут повысить производительность и функциональность ваших приложений.
Если вы хотите узнать больше о Core Data или детально взглянуть на структуру фреймворка, то документация для разработчиков Apple — идеальное место.
Ресурсы
Документация для разработчиков Apple:
Введение в программирование основных данных