スレッド毎にNSManagedObjectContextを管理する

基本的にはNSManagedObjectContextはスレッド毎に作成して利用しなければいけません。以下は、その為にスレッド毎にContextを作成して管理するためのコードです。複雑なアプリでなければ、この管理法で十分でしょう。シングルトンなManagerクラスでコンテキストを管理する方法は簡単ですが、スレッドを多用する場合はスレッド毎に管理する方が良いでしょう。

managedObjectContextForCurrentThreadでコンテキストを取得し、保存は[NSManagedObjectContext save:&error]のように、クラスメソッドを呼びます。保存するとNSNotificationでコンテキスト自身のmanagedObjectContextDidSave:が実行され、メインスレッドのコンテキストに変更が反映されます。スレッド毎に自動的にthreadDictionaryにNSManagedObjectContextを登録し、スレッドが破棄されるとコンテキストも破棄されるようになっているので、明示的にコンテキストを破棄する必要はありません。

@interface NSManagedObjectContext (Extras)
+ (NSManagedObjectContext *)managedObjectContextForThread:(NSThread *)thread;
+ (NSManagedObjectContext *)managedObjectContextForCurrentThread;
+ (NSManagedObjectContext *)managedObjectContextForMainThread;
+ (BOOL)save:(NSError **)error;
@end
#import "NSManagedObjectContextExtras.h"
 
NSString * const NSManagedObjectContextThreadKey = @"NSManagedObjectContextThreadKey";
 
@interface NSManagedObjectContext ()
- (void)managedObjectContextDidSave:(NSNotification*)notification;
@end
 
@implementation NSManagedObjectContext (Extras)
 
+ (NSManagedObjectContext *)managedObjectContextForThread:(NSThread *)thread {
    NSMutableDictionary *threadDictionary = [thread threadDictionary];
    NSManagedObjectContext *context = [threadDictionary objectForKey:NSManagedObjectContextThreadKey];
 
    if (!context) {
#ifdef TARGET_OS_IPHONE
        id appDelegate = [[UIApplication sharedApplication] delegate];
#else
        id appDelegate = [NSApp delegate];
#endif
        NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
 
        if ([[NSThread currentThread] isMainThread]) {
            context = mainContext;
        } else {
            context = [[[NSManagedObjectContext alloc] init] autorelease];
            [context setPersistentStoreCoordinator:[mainContext persistentStoreCoordinator]];
        }
 
        [threadDictionary setObject:context forKey:NSManagedObjectContextThreadKey];
    }
 
    return context;
}
 
+ (NSManagedObjectContext *)managedObjectContextForCurrentThread {
    return [NSManagedObjectContext managedObjectContextForThread:[NSThread currentThread]];
}
 
+ (NSManagedObjectContext *)managedObjectContextForMainThread {
    return [NSManagedObjectContext managedObjectContextForThread:[NSThread mainThread]];
}
 
+ (BOOL)save:(NSError **)error {
    NSManagedObjectContext *context = [NSManagedObjectContext managedObjectContextForCurrentThread];
    BOOL isMainThread = [[NSThread currentThread] isMainThread];
 
    if (!isMainThread) {
        [[NSNotificationCenter defaultCenter] addObserver:context
                                                 selector:@selector(managedObjectContextDidSave:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:context];
    }
 
    BOOL result = [context save:error];
 
    if (!isMainThread) {
        [[NSNotificationCenter defaultCenter] removeObserver:context
                                                        name:NSManagedObjectContextDidSaveNotification 
                                                      object:context];
    }
 
    return result;
}
 
- (void)managedObjectContextDidSave:(NSNotification*)notification {
    NSManagedObjectContext *context = [NSManagedObjectContext managedObjectContextForMainThread];
 
    [context performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
                              withObject:notification
                           waitUntilDone:YES];
}
@end

mogeneratorを利用してCoreDataクラスを編集・再生成しやすくする

mogeneratorとは

mogeneratorはCoreDataエンティティのカスタムクラスを生成するツールです。

定義したエンティティをもとにクラスファイルをXcodeで生成しますが、開発者がコードを追加したあとで再度クラスファイルを生成すると、マージ作業が必要になります。

mogeneratorはこれを回避するためにFooエンティティに対して_FooクラスとそのサブクラスFooクラスを生成します。開発者はFooを書き換えることで、_Fooが何度生成されても独自コードをマージする必要が無くなります。

つまり懐かしのEOGeneratorのCoreData版です。

導入方法

mogeneratorのページからパッケージをダウンロードしてインストールします。/usr/bin/mogeneratorがインストールされます。

下記のようにモデルファイルを指定して、-Oでファイルを作成するフォルダを指定して実行すると、ファイルが生成されます。-Oを指定しない場合はカレントフォルダに生成されます。

$ mogenerator -m MyProject.xcdatamodeld/MyProject.xcdatamodel -O ./
4 machine files and 4 human files generated.

作成されたファイルをXcodeプロジェクトに追加します。今後カスタムロジックはFooクラスに追加し、エンティティに変更を加えた場合にはmogeneratorを再度実行して_Fooクラスを再生成します。