4 Core Data для Mac и iOS. Общая картина

Core Data происходит от Enterprise Objects Framework (EOF) - разработки NeXT как части WebObjects в 1990-х. В 1996 после того как Apple купила NeXT, EOF трансформируется в Core Data framework и выпускается в составе Mac OS X 10.4 (Tiger) и позднее iOS.

Главная цель скомбинировать ООП и РСУБД.

Объектно-ориентированные базы данных (еще их называют объектые базы данных) были созданы в 1980-х годах.

Core Data хранит данные используя persistence store:

  • SQLite на Mac или iOS, 
  • XML только на Mac. 

В качестве data management tool используется Core Data model editor (файлы с расширением .xcdatamodeld) в Xcode.


Xcode автоматически сгенерирует код для сущности Place:

//
//  Place.h
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@interface Place : NSManagedObject
@property (nonatomic, retain) NSString * address;
@property (nonatomic, retain) NSSet *event;
@end
@interface Place (CoreDataGeneratedAccessors)
- (void)addEventObject:(NSManagedObject *)value;
- (void)removeEventObject:(NSManagedObject *)value;
- (void)addEvent:(NSSet *)values;
- (void)removeEvent:(NSSet *)values;
@end

//
//  Place.m
//
#import “Place.h”
@implementation Place
@dynamic address;
@dynamic event;
@end

Обратите внимание, что класс унаследован от NSManagedObject. Вместо @synthesize используется @dynamic. Managed object - это объект созданный из сущности модели данных.

SQLite рассчитан на одного пользователя. Он определенно может использоваться в многопользовательской среде, но сегодня в основном используется на одного пользователя.

Core Data model - это схема БД - описание строк и колонок или записей и полей по другому.

В runtime режиме Core Data stack состоит из

  • managed object context, 
  • managed object model, 
  • и persistent store coordinator.


В режиме runtime, модель данных комбинируется с двумя другими объектами:

  • managed object context (NSManagedObjectContext
  • и persistent object store (NSPersistentStore) - обычно SQLite БД, но может быть и другой тип хранилища.


В runtime, каждый managed object является частью managed object context. Можно иметь любое количество managed object contexts в приложении в любое время. Одиночная сущность из модели данных может быть создана в нескольких экземплярах managed objects, но каждый экземпляр managed object должен быть в отдельном managed object context. При сохранении изменений сохраняется managed object context. Данные сохраняются в persistent store. Использование множества managed object contexts помогает реализовать undo и redo функции.

managed object - это объект созданный из сущности из модели данных и возможно из данных хранящихся в persistent store.

persistent object store в большинстве случаев это файл базы данных. В нем делается mapping между БД и объектами в режиме runtime. Также как можно иметь множество managed object contexts, также можно иметь множество persistent stores. В случае с persistent stores, NSPersistentStoreCoordinator выполняет управление; для управления managed object contexts нет соответсвующего объекта.

В вашем коде вы работаете с

  • managed objects, 
  • persistent store coordinator, 
  • и managed object context. 

Это самый просто сценарий. Если вам нужно множество managed object contexts, то можно их создать например для реализации undo команд. It will be the managed object context that actually commits a change to a persistent store.

В простейшем случае persistent store coordinator “координирует” одиночное хранилище.

Рассмотрим Master-Detail Application–based template в iOS или Cocoa Application template под Mac OS, в обоих случаях с Core Data.

//
//  AppDelegate.h
//
#import <UIKit/UIKit.h>
@interface AppDelegate : 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;
@property (strong, nonatomic) UINavigationController *navigationController;
@property (strong, nonatomic) UISplitViewController *splitViewController;
@end

Реализация:
@implementation AppDelegate
@synthesize window = _window;
@synthesize managedObjectContext = __managedObjectContext;
@synthesize managedObjectModel = __managedObjectModel;
@synthesize persistentStoreCoordinator = __persistentStoreCoordinator; 
@synthesize navigationController = _navigationController;
@synthesize splitViewController = _splitViewController;

- (NSManagedObjectContext *)managedObjectContext 
{
if (__managedObjectContext != nil) 
{
 return __managedObjectContext; 
}
// Set a local variable to the persistent store coordinator 
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) 
{
// Create the managed object context with the persistent store coordinator 
__managedObjectContext = [[NSManagedObjectContext alloc] init]; 
__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext; 
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator 
{
if (__persistentStoreCoordinator != nil) 
{
 return __persistentStoreCoordinator; 
}
NSURL *storeURL = [[self applicationDocumentsDirectory]
URLByAppendingPathComponent:
// HERE IS THE NAME OF YOUR DATABASE FILE
@”My_First_iOS_Project.sqlite”];
NSError *error = nil;
__persistentStoreCoordinator
[[NSPersistentStoreCoordinator alloc]
// PICK UP THE MANAGED OBJECT MODEL FOR THE PERSISTENT STORE COORDINATOR
  initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator
 addPersistentStoreWithType:NSSQLiteStoreType
    configuration:nil URL:storeURL options:nil
error:&error]) 
{
 NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);
 abort(); 
}
return __persistentStoreCoordinator; 
}

- (NSManagedObjectModel *)managedObjectModel
{
if (__managedObjectModel != nil) 
{
return __managedObjectModel; 
}
NSURL *modelURL = [[NSBundle mainBundle]
  URLForResource:@”My_First_iOS_Project “ withExtension:@”momd”];
__managedObjectModel = [[NSManagedObjectModel alloc]
  initWithContentsOfURL:modelURL];
return __managedObjectModel; 
}

@end

Core Data model editor позволяет создавать множество версий модели. По умолчанию используется последняя, но можно изменить это.

Чтобы сделать запрос нужно создать fetch request и fetch request controller. Он будет взаимодействовать с Core Data stack.

При использовании для отображения данных UITableView, протокол для предоставления функциональности источника данных будет взаимодействовать с fetch request.

Класс MasterViewController принимает протокол NSFetched ResultsControllerDelegate. Данный протокол позволяет NSFetchedResultsController уведомлять его delegate о том что fetch результаты изменились.

- (NSFetchedResultsController *)fetchedResultsController
{
  if (__fetchedResultsController != nil) {
    return __fetchedResultsController;
}
  // Set up the fetched results controller.
  // Create the fetch request for the entity.
  NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
  // Edit the entity name as appropriate.
  NSEntityDescription *entity = [NSEntityDescription entityForName:@”Event”
    inManagedObjectContext:self.managedObjectContext];
  [fetchRequest setEntity:entity];
  // Set the batch size to a suitable number.
  [fetchRequest setFetchBatchSize:20];
  // Edit the sort key as appropriate.
  NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:
  @”timeStamp”
    ascending:NO];
  NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
  [fetchRequest setSortDescriptors:sortDescriptors];
  // Edit the section name key path and cache name if appropriate.
  // nil for section name key path means “no sections”.
  NSFetchedResultsController *aFetchedResultsController =
    [[NSFetchedResultsController alloc]
    initWithFetchRequest:fetchRequest
    managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil
    cacheName:@”Master”];
  aFetchedResultsController.delegate = self;
  self.fetchedResultsController = aFetchedResultsController;
 NSError *error = nil;
  if (![self.fetchedResultsController performFetch:&error]) {
    /*
    Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
    */
    NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);
    abort();
}
  return __fetchedResultsController;
}