ページ

2009年12月28日月曜日

Core Data : 複数の NSManagedObjectContext を使う - Optimistic Locking

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

複数の NSManagedObjectContext

Core Data では一つのDBに対して複数の NSManagedObjectContext を使うことができる。NSManagedObjectContext を複数使うケースとしては、例えば同じDBを扱う複数のビューを用意して編集途中の状況をお互いに影響与えないような使い方が考えられる。編集が確定した時だけ他のビューへその変更内容を反映する。


Optimistic Locking

ただしこの場合、複数のビューで同時に編集が行われるとデータに不整合が生じる恐れがある。Core Data ではこのような不整合を防ぐ為に Optimistic Locking(楽観的ロック)を採用している。Optimistic Locking では編集前に排他的ロックをかけることは行わず、編集を確定(commit)する時に不整合をチェックする。もし不整合が生じることが検出された場合は確定処理を失敗させる。Core Data では NSManagedObjectContext がデータを取得した時にバージョンも併せて保持していて、編集確定時に対象となるバージョンが一致していなければ(すなわち変更されている)不整合と判断する。


サンプルプログラム作成

このあたりの挙動をサンプルプログラムを作って確認してみよう。
(以下、Mac OS X 10.6 / Xcode 3.2 で実施)

まずテンプレートから  Cocoa Application (Use Core Data for storage)を選びプロジェクトを作成する。



AppDelegateクラスに2つの NSManagedObjectContext を追加する。

@interface CoreDataConflict_AppDelegate : NSObject
{
    NSWindow *window;
  
    NSPersistentStoreCoordinator *persistentStoreCoordinator;
    NSManagedObjectModel *managedObjectModel;
    NSManagedObjectContext *managedObjectContext;

NSManagedObjectContext* moc1;
NSManagedObjectContext* moc2;

}
:

@property (nonatomic, retain, readonly) NSManagedObjectContext *moc1;
@property (nonatomic, retain, readonly) NSManagedObjectContext *moc2;



Getメソッドは自動生成せず自前で用意する。

- (NSManagedObjectContext *)moc1
{
if (moc1) {
return moc1;
}
NSPersistentStoreCoordinator* psc = [self persistentStoreCoordinator];
moc1 = [[NSManagedObjectContext alloc] init];
[moc1 setPersistentStoreCoordinator:psc];
return moc1;
}
- (NSManagedObjectContext *)moc2
{
if (moc2) {
return moc2;
}
NSPersistentStoreCoordinator* psc = [self persistentStoreCoordinator];
moc2 = [[NSManagedObjectContext alloc] init];
[moc2 setPersistentStoreCoordinator:psc];
return moc2;
}


データモデル用に Book クラス(NSManagedObject)を作る。

@interface Book :  NSManagedObject
{
}

@property (nonatomic, retain) NSString * author;
@property (nonatomic, retain) NSString * title;

@end



続いて Interface Builder を開き2つのウィンドウとテーブルビューを用意する。

NSArrayController をそれぞれに用意し、先ほどの NSManagedObjectContext へ接続する。

NSArrayController の接続状況(bindings)はこんな感じ。


実行

実行してみよう。起動するとテスト用データ1件が表示される。

両方の Title を変更し保存する(ctrl+S)。

すると2番目の NSManagedObjectContext(moc2)でエラーが検出された。

CoreDataConflict[11054:80f] moc2 error:
 Error Domain=NSCocoaErrorDomain Code=133020
 UserInfo=0xe37400 "Could not merge changes."

詳しい情報が UserInfo に入っているので見てみる。

CoreDataConflict[11054:80f] UserInfo: {
    conflictList =     (
                {
            cachedRow =             {
                author = "Stephen G. Kochan";
                title = "TEST TITLE 1";
            };
            newVersion = 2;
            object = " (entity: Book; id: 0xe11750 
                <x-coredata://071C66C0-BFAA-44AE-8C84-52E2B92C5FCB/Book/p1> ;
                data: {\n    author = \"Stephen G. Kochan\";
                       \n    title = \"TEST TITLE 2\";\n})";
            oldVersion = 1;
            snapshot =             {
                author = "Stephen G. Kochan";
                title = "Programming in Objective-C 2.0";
            };
        }
    );
}


問題となっているレコードが表示されてバージョンが異なっているのがわかる。


ソースコード

github からどうぞ
CoreDataConflict at SD0001 from xcatsan's SampleCode - GitHub


参考

Mac Dev Center: Core Data Programming Guide: Change Management

今回の検証のネタもと。複数 NSManagedObjectContext や Optimistic Locking について説明されている。