Структурирование приложений для Core Data, Documents, и Shoeboxes

Мы уже рассмотрели как настраивается Core Data стек. К примеру вы всегда имеете managed object context и persistent store. Разумеется у вас есть модель данных (которая приблизительно совпадает со схемой БД). Также у вас есть SQLite-файл содержащий данные persistent store. Core Data можно настроить на работу не только с SQLite.

Фреймворки Cocoa для Mac OS и Cocoa Touch для iOS эволюционировали из  NeXTSTEP и OpenStep разработанные компанией NeXT (куплена Apple в 1995 году). Также они эволюционировали из раних фреймворков и сред разработки, которые разрабатываются в Apple с 1984 года.

Можно предоставить Core Data stack (data model, managed object context, и persistent store) для отдельного документа. Это позволит работать с данными на уровне документов. Если один и тот же документ открыть в двух окнах, то каждое окно будет иметь собственный Core Data stack.

Есть возможность обеспечить версионность документов.

Library/shoebox приложения вместо документов используют persistent store, который поддерживается самим приложением. Например, в iPhoto или iCal ваши данные хранятся в единственном файле, который сокрыт в папке Library/Application Support/.

Одну и ту же модель данных можно использовать как на Mac OS так и на iOS.

Создание Mac OS Library/Shoebox-Based приложения

Создадим новый Xcode-проект:

В Objective-C для расширения функциональности классов часто используются делегаты. В других языках применяю наследование. В данном случае при создании нового проекта создался делегат AppDelegate.


#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate>

@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;


@end


Метод windowWillReturnUndoManager: возвращает undo manager для managed object context.


- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window {
    // Returns the NSUndoManager for the application. In this case, the manager returned is that of the managed object context for the application.
    return [[self managedObjectContext] undoManager];
}

Таким образом Core Data управляет операциями undo и redo в окне отображающем данные.

Метод saveAction: коммитит изменения и сохраняет данные в managed object context.


- (IBAction)saveAction:(id)sender {
    // Performs the save action for the application, which is to send the save: message to the application's managed object context. Any encountered errors are presented to the user.
    if (![[self managedObjectContext] commitEditing]) {
        NSLog(@"%@:%@ unable to commit editing before saving", [self class], NSStringFromSelector(_cmd));
    }
    
    NSError *error = nil;
    if ([[self managedObjectContext] hasChanges] && ![[self managedObjectContext] save:&error]) {
        [[NSApplication sharedApplication] presentError:error];
    }
}


Создание iOS Library/Shoebox-Based приложения

Создадим Master-Detail приложение.

Базовая структура приложения аналогична той, что мы видели под Mac OS. Делегат приложения AppDelegate унаследован от класса UIResponder и принимает протокол UIApplicationDelegate.


#import <UIKit/UIKit.h>
#import <CoreData/CoreData.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;


@end

Тут вместо saveAction: указан saveContext. И добавлен метод доступа applicationDocumentsDirectory.

Создание Mac OS Document-Based приложения

Создадим новый Xcode-проект:

В созданном проекте файлы Document.h и Document.m представляют собой класс проекта. Данный унаследован от NSPersistentDocument.

В файле Document.xcdatamodeld содержится пустая модель данных.

По умолчанию на Mac OS дается три типа документов: бинарный, XML и SQLite.
Если вы собираетесь использовать только SQLite, то остальные два можно удалить. Это упростит работу с приложением т.к. пользователю при сохранении документа не придется выбирать его тип.

Класс документа должен имплементировать Core Data stack.


#import <Cocoa/Cocoa.h>
@interface MyDocument : NSPersistentDocument {
NSManagedObject *customer;
}
- (NSManagedObject *)customer;
- (void)setCustomer:(NSManagedObject *)aCustomer;
@end





#import “MyDocument.h”
@implementation MyDocument
#pragma mark - Application life cycle
- (id)init
{
self = [super init];
if (self) {
// Add your subclass-specific initialization here.
// If an error occurs here, return nil.
}
return self;
}
- (id)initWithType:(NSString *)type error:(NSError **)error
{
self = [super initWithType:type error:error];
if (self != nil)
{
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
[self setCustomer:[NSEntityDescription
insertNewObjectForEntityForName:@”Customer
inManagedObjectContext:managedObjectContext]];
// To avoid undo registration for this insertion, removeAllActions on the
// undoManager.
// First call processPendingChanges on the managed object context to force
// the undo registration
// for this insertion, then call removeAllActions.
[managedObjectContext processPendingChanges];
[[managedObjectContext undoManager] removeAllActions];
[self updateChangeCount:NSChangeCleared];
}
return self;
}
#pragma mark - View/window life cycle
- (NSString *)windowNibName
{
// Override returning the nib file name of the document
// If you need to use a subclass of NSWindowController or if your document
supports
// multiple NSWindowControllers, you should remove this method and override -
// makeWindowControllers instead.
return @”MyDocument;
}
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
[super windowControllerDidLoadNib:aController];
// Add any code here that needs to be executed once the windowController has
loaded
// the document’s window.
}
#pragma mark - Autosave and versions
+ (BOOL)autosavesInPlace
{
return YES;
}

#pragma mark - Core Data stack
- (NSManagedObject *)customer
{
if (customer != nil)
{
return customer;
}
NSManagedObjectContext *moc = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSError *fetchError = nil;
NSArray *fetchResults;
@try
{
NSEntityDescription *entity = [NSEntityDescription entityForName:@”Customer
inManagedObjectContext:moc];
[fetchRequest setEntity:entity];
fetchResults = [moc executeFetchRequest:fetchRequest error:&fetchError];
} @finally
{
//[fetchRequest release];
}
if ((fetchResults != nil) && ([fetchResults count] == 1) && (fetchError == nil))
{
[self setCustomer:[fetchResults objectAtIndex:0]];
return customer;
}
if (fetchError != nil)
{
[self presentError:fetchError];
}
else {
// should present custom error message...
}
return nil;
}
- (void)setCustomer:(NSManagedObject *)aCustomer
{
if (customer != aCustomer)
{
customer = aCustomer;
}
}
@end

Перемещение моделей данных

Core Data не является много пользовательской средой. Но возможно переносить persistent store между различными устройствами и обновлять его.