Обеспечение потокобезопасности Core Data

Короче говоря, я устал от абсурдных правил параллелизма, связанных с 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. = ;. Мне не ясно, в чем проблема, потому что если я печатаю objв отладчике, все выглядит хорошо. Любые предложения о том, в чем может быть проблема (, кроме того факта, что Apple рекомендует не создавать подклассы NSManagedObjectContext) и как ее исправить?

П.С. Я знаю о GCD и NSOperationQueueи других методах, обычно используемых для "решения" этой проблемы. Ни один из них не предлагает то, что я хочу. Я ищу NSManagedObjectContext, который может свободно, безопасно и напрямую использоваться любым количеством потоков для просмотра и изменения состояния приложения без необходимости какой-либо внешней синхронизации.

13
задан aroth 4 July 2012 в 01:03
поделиться