长话短说,我厌倦了与相关的荒谬并发规则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];
}
}
所以本质上,我启动了一些针对上述入口点的线程,它们随机创建和删除实体。这几乎可以正常工作。
问题是,每隔一段时间,一个线程就会EXC_BAD_ACCESS
在调用时得到一个obj.<field> = <value>;
。我不清楚问题是什么,因为如果我obj
在调试器中打印,一切看起来都很好。关于问题可能是什么的任何建议(除了Apple建议不要继承 NSManagedObjectContext 的事实)以及如何解决它?
PS 我知道 GCD 和NSOperationQueue
其他通常用于“解决”这个问题的技术。这些都没有提供我想要的东西。我正在寻找的是一个NSManagedObjectContext
可以自由、安全、直接地被任意数量的线程用来查看和更改应用程序状态而不需要任何外部同步的。