ПРОГРАММИРОВАНИЕ ПОД IPHONE, IPAD OBJECTIVE-C часть 3

CoreData

На сегодняшний день хранение данных в различных базах (включая СУБД, SQLite и пр.) являются очень актуальными. Core Data представляет из себя контейнер хранилище данных при разработке приложений для iOS. Они хранятся в бинарном виде (так называемом Core Data формате: xcdatamodeld).

 

Ниже я перечислил основные возможности Core Data:

 

  • Операции со сложными графами объектов;
  • Операции с множественными связями;
  • Операции с валидацией.

 

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

 

При создании Core Data, разработчики из Apple выделили четыре стека:

 

  • NSManagedObjectContext
  • NSManagedObjectModel
  • NSPersistentStoreCoordinator
  • NSPersistentStore

 

Давайте рассмотрим каждый из них.

 

Принимая во внимания различные системы БД (в том числе и СУБД), то Managed Object Context (MOC) – это транзакция. То есть, все объекты попадают в хранилище (БД) и из него, только через MOC. Контекстов можно создавать несколько, и каждый привязан кNSPersistenceStoreCoordinator, и знает куда сохранять и, соответственно, откуда загружать данные.

 

В библиотеке CoreData представлена Managed Object Model классом NSManagedObjectModel. Судя по названию эта модель есть ни что иное как схема базы данных (если мы работаем с БД, конечно). То есть хранит информацию о всех сущностях, о их связях, зависимостях, свойствах, запросах. Для упрощения создания такой модели, в Xcode присутствует специальный редактор. Файлы моделей в проекте имеют расширение .xcdatamodel, и затем компилируются в .mom файлы, уже из которых можно инициализироватьNSManagedObjectModel.

 

NSPersistentStoreCoordinator дает возможность использовать одновременно несколько хранилищ объектов(PersistentStore).

 

NSPersistenceStore отвечает за то, в каком виде хранятся объекты , и где они хранятся. CoreData умеет работать с четырьмя видами хранилищ – XML, SQLite, Binary и inMemory. Для использования Core Data, мы должны добавить в проект CoreData.framework. Однако если вы создадим проект с поддержкой CoreData — он добавится автоматически.

 

Перейдем от теории к практике. Создадим новый проект на базе шаблона Master-Detail Application и назовем его CoreNotes (обязательно поставьте галочку Use Core Data при создании проекта). Мы создадим пример аналогичный SQLite, то есть, простенькую записную книгу. Но сделаем теперь это с помощью CoreData. Если вы запустите проект — то увидите, что основную работу по конфигурированию базы за нас проделал Xcode. Реализована возможность добавления и удаления записей. В каждой записи всего одно поле timeStamp, в котором хранится дата его создания. Поскольку наше приложение будет выполнять функцию записной книги — одного поля даты нам не достаточно. Поэтому перейдем в CoreNotes.xcdatamodeld, в котором храниться визуальная схема нашей базы данных и ее свойства. Там найдите сущьность Event, а в правом окне перед вами откроется список ее атрибутов. В этом списке вы и найдете вышеупомянутый timeStamp. Добавим еще один атрибут с названием note и типом String. В результате конфигурация ваше базы должна выглядеть примерно так:

 

 

Наша база данных готова к работе. Но перед тем как приступить непостердственно к процессу программирования, следует уточнить пару моментов. В первую очередь, следует понять, что такое NSManagedObjectContext? Это так называемый «блокнот» для ключевых данных в приложении, а также для управления этими данными. Кроме этого, он управляет несколькими основными чертами в ключевых данных, включая проверка, отмена / повтор и управление записями. Управляемый объект контекста соединяется между кодом и хранилищем данных. Выполняется NSManagedObjectContext, затем харнилище NSPersistentStoreCoordinator, который отвечает за хранение объектов данных. Все эти технологии позволяют Core Data быть очень гибким в хранении данных. Схематически, всю эту модель можно представить так:

 

 

Сделаем наш код рабочим, а затем разберем по пунктам что означает каждый метод. Наша записная книга выводит записи в таблицу. Чтобы в ячейке отображалась дата записи и текст — следует изменить тип ячейки на UITableViewCellStyleSubtitle. Сделать это следует в методе cellForRowAtIndexPath:

 

<code data-result="[object Object]">- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle 
                                      reuseIdentifier:CellIdentifier];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }

    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}</code>

 

Соответственно, следует исправить метод configureCell, который заполняет ячейку данными (добавим в него возможность чтения атрибута note):

 

<code data-result="[object Object]">- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = [[managedObject valueForKey:@"note"] description];
    cell.detailTextLabel.text = [[managedObject valueForKey:@"timeStamp"] description];
}</code>

 

Метод создания записи (insertNewObject) нам тоже нужно изменить с учетом еще одного атрибута:

 

<code data-result="[object Object]">- (void)insertNewObject
{
    NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
    NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
    NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] 
                                                                      inManagedObjectContext:context];

    [newManagedObject setValue:[NSDate date] forKey:@"timeStamp"];
    [newManagedObject setValue:@"iMaladec" forKey:@"note"];

    [self saveContext];
}</code>

 

Ту часть кода, которая выполняет сохранение контекста, я вынес в отдельный метод (он нам пригодиться по внесения изменений в запись):

 

<code data-result="[object Object]">- (void)saveContext {
    NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];

    NSError *error = nil;
    if (![context save:&amp;error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
}</code>

 

Редактировать записи мы будем в классе DetailViewController, но поскольку метод сохранения у нас находится в MasterViewController — нам прийдется использовать механизм делегирования. Для этого изменим метод перехода к редактированию (didSelectRowAtIndexPath):

 

<code data-result="[object Object]">- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (!self.detailViewController) {
        self.detailViewController = [[DetailViewController alloc] initWithNibName:@"DetailViewController" 
                                                                           bundle:nil];
    }
    NSManagedObject *selectedObject = [[self fetchedResultsController] objectAtIndexPath:indexPath];
    self.detailViewController.detailItem = selectedObject;    
    self.detailViewController.delegate = self;
    [self.navigationController pushViewController:self.detailViewController animated:YES];
}</code>

 

А сам класс DetailViewController вам должен быть знаком с урока SQLite:

 

DetailViewController.h

<code data-result="[object Object]">#import &lt;UIKit/UIKit.h&gt;

@interface DetailViewController : UIViewController

@property (assign, nonatomic) id delegate;
@property (strong, nonatomic) id detailItem;
@property (strong, nonatomic) IBOutlet UITextView *note;

@end</code>

 

DetailViewController.m

<code data-result="[object Object]">#import "DetailViewController.h"

@implementation DetailViewController

@synthesize delegate;
@synthesize detailItem;
@synthesize note;

- (void)viewDidUnload
{
    [super viewDidUnload];
    self.note = nil;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIBarButtonItem *done =[[UIBarButtonItem alloc] 
                             initWithBarButtonSystemItem:UIBarButtonSystemItemDone
                             target:self
                             action:@selector(pressDone)];
	self.navigationItem.rightBarButtonItem = done; 

    note.text = [[detailItem valueForKey:@"note"] description];
    self.title = [[detailItem valueForKey:@"timeStamp"] description];
}

- (void)pressDone {
    [detailItem setValue:note.text forKey:@"note"];

    if ([delegate respondsToSelector:@selector(saveContext)]) {
        [delegate performSelector:@selector(saveContext)];
    }

    [self.navigationController popViewControllerAnimated:YES];	
}

@end</code>

 

Основное отличие от старого урока заключается в том, что значения даты и текста мы получаем с detailItem, как это делали в методе configureCell класса MasterViewController. А в методе сохранения устанавливаем новое значение и вызываем функцию сохранения saveContext. Если кто-то забыл как пользоваться делегатами — можно почитать в соответствующем уроке Делегирование.

 

Наше приложение готово к работе. Теперь разберем то, что осталось за кулисами.

В методе viewDidLoad добавляем элементы на NavigationBar и устанавливаем им значения. Правая кнопка, которую мы на него добавляем вызывает уже знакомую функцию insertNewObject, которая, как я уже говорил, добавляет новую запись в базу. Мы создаем новую запись событий, которая называется NSEntityDescription. Это ваша строка в базе данных для новой записи. За это отвечает классNSManagedObjectContext. За дату в таблице будет отвечать атрибут timeStamp который мы задавали в нашей схеме CoreData, а за текст — note. После внесения всех изменений происходит операция сохранения. В момент добавления новой записи вызывается метод didChangeSection протокола NSFetchedResultsControllerDelegate. Как раз в нем мы и добавляем новую ячейку в таблицу.

 

Извлечение данных с хранилища происходит в методе fetchedResultsController. В нем мы создаем запрос, в роли которого выступает NSFetchRequest. Мы можем определить условия согласования конкретного свойства в зависимости от сортировки записей. Процесс создание нового запроса NSFetch достаточно прост. Вам просто нужно определить объект, который необходим для  записи вNSManagedObjectContext. Я не буду переписывать этот метод, поскольку Xcode его добавляет автоматически и вы самостоятельно можете его разобрать.

 

В методе numberOfSectionsInTableView мы возвращаем количество секций результата нашего запроса. А в numberOfRowsInSection — количество объектов в каждой секции.

 

Кроме вышеуказаных методов, вы можете заглянуть в AppDelegate. Там можно найти код который добавил Xcode автоматически. Он необходим для управления базой данных (ее создания, загрузки и сохранения). При необходимости можете разобраться в нем.

 

 

Comments are closed.