Короче говоря, я устал от абсурдных правил concurrency, связанных с NSManagedObjectContext
(или, вернее, его полного отсутствия поддержки concurrency и склонности к взрыву или другим неправильным вещам, если вы попытаетесь поделиться a 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];
}
}
Итак, по существу, я выделяю некоторые потоки, нацеленные на указанную выше точку входа, и они произвольно создают и удаляют сущности. Это почти работает так, как должно быть.
Проблема заключается в том, что каждый так часто один из потоков получает EXC_BAD_ACCESS
при вызове obj.<field> = <value>;
. Мне непонятно, в чем проблема, потому что, если я печатаю obj
в отладчике, все выглядит хорошо. Любые предложения о том, что может быть проблемой (кроме того, что Apple рекомендует против подкласса NSManagedObjectContext) и как его исправить?
P.S. Я знаю GCD и NSOperationQueue
и другие методы, обычно используемые для "решения" этой проблемы. Никто из них не предлагает то, что я хочу. Я ищу NSManagedObjectContext
, который может быть свободно, безопасно и напрямую использоваться любым количеством потоков для просмотра и изменения состояния приложения без необходимости внешней синхронизации.