簡単に言えば、関連するばかげた同時実行ルール(というか、同時実行のサポートが完全に欠如していて、スレッド間NSManagedObjectContext
で共有しようとすると爆発したり、他の間違ったことをしたりする傾向があること)にうんざりしており、実装しようとしています。NSManagedObjectContext
スレッドセーフなバリアント。
基本的に、作成されたスレッドを追跡するサブクラスを作成し、すべてのメソッド呼び出しをそのスレッドにマップします。これを行うメカニズムは少し複雑ですが、重要なのは、次のようないくつかのヘルパー メソッドがあることです。
- (NSInvocation*) invocationWithSelector:(SEL)selector {
//creates an NSInvocation for the given selector
NSMethodSignature* sig = [self methodSignatureForSelector:selector];
NSInvocation* call = [NSInvocation invocationWithMethodSignature:sig];
[call retainArguments];
call.target = self;
call.selector = selector;
return call;
}
- (void) runInvocationOnContextThread:(NSInvocation*)invocation {
//performs an NSInvocation on the thread associated with this context
NSThread* currentThread = [NSThread currentThread];
if (currentThread != myThread) {
//call over to the correct thread
[self performSelector:@selector(runInvocationOnContextThread:) onThread:myThread withObject:invocation waitUntilDone:YES];
}
else {
//we're okay to invoke the target now
[invocation invoke];
}
}
- (id) runInvocationReturningObject:(NSInvocation*) call {
//returns object types only
[self runInvocationOnContextThread:call];
//now grab the return value
__unsafe_unretained id result = nil;
[call getReturnValue:&result];
return result;
}
...そして、サブクラスはNSManagedContext
次のようなパターンに従ってインターフェイスを実装します。
- (NSArray*) executeFetchRequest:(NSFetchRequest *)request error:(NSError *__autoreleasing *)error {
//if we're on the context thread, we can directly call the superclass
if ([NSThread currentThread] == myThread) {
return [super executeFetchRequest:request error:error];
}
//if we get here, we need to remap the invocation back to the context thread
@synchronized(self) {
//execute the call on the correct thread for this context
NSInvocation* call = [self invocationWithSelector:@selector(executeFetchRequest:error:) andArg:request];
[call setArgument:&error atIndex:3];
return [self runInvocationReturningObject:call];
}
}
...そして、次のようなコードでテストしています:
- (void) testContext:(NSManagedObjectContext*) context {
while (true) {
if (arc4random() % 2 == 0) {
//insert
MyEntity* obj = [NSEntityDescription insertNewObjectForEntityForName:@"MyEntity" inManagedObjectContext:context];
obj.someNumber = [NSNumber numberWithDouble:1.0];
obj.anotherNumber = [NSNumber numberWithDouble:1.0];
obj.aString = [NSString stringWithFormat:@"%d", arc4random()];
[context refreshObject:obj mergeChanges:YES];
[context save:nil];
}
else {
//delete
NSArray* others = [context fetchObjectsForEntityName:@"MyEntity"];
if ([others lastObject]) {
MyEntity* target = [others lastObject];
[context deleteObject:target];
[context save:nil];
}
}
[NSThread sleepForTimeInterval:0.1];
}
}
基本的に、上記のエントリ ポイントを対象とするいくつかのスレッドを起動すると、エンティティがランダムに作成および削除されます。これはほとんど正常に機能します。
問題は、スレッドの 1 つがEXC_BAD_ACCESS
呼び出し時に頻繁に を取得することobj.<field> = <value>;
です。obj
デバッガーで印刷するとすべてがうまく見えるので、何が問題なのかはっきりしません。問題の可能性 ( Apple が NSManagedObjectContext のサブクラス化を推奨していないという事実以外) とその修正方法に関する提案はありますか?
PS私は、NSOperationQueue
この問題を「解決」するために通常使用されるGCDおよびその他の手法を認識しています。それらのどれも私が欲しいものを提供していません。私が探しているのは、NSManagedObjectContext
外部同期を必要とせずにアプリケーションの状態を表示および変更するために、任意の数のスレッドで自由に、安全に、直接使用できる です。