Core Data Thread-sicher machen

Um es kurz zu machen, ich bin es leid, die absurden Regeln der Parallelität zu kennen, die damit verbunden sindNSManagedObjectContext (oder besser gesagt, es gibt keine Unterstützung für Parallelität und die Tendenz, zu explodieren oder andere falsche Dinge zu tun, wenn Sie versuchen, eine gemeinsam zu nutzenNSManagedObjectContext across threads) und versuche eine thread-sichere Variante zu implementieren.

Grundsätzlich habe ich eine Unterklasse erstellt, die den Thread verfolgt, für den sie erstellt wurde, und dann alle Methodenaufrufe diesem Thread zuordnet. Der Mechanismus dafür ist etwas kompliziert, aber der springende Punkt ist, dass ich einige Hilfsmethoden habe wie:

- (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;
}

... und dann implementiert die Unterklasse dieNSManagedContext Schnittstelle nach einem Muster wie:

- (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];
    }
}

... und dann teste ich es mit folgendem Code:

- (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];
    }
}

Im Wesentlichen drehe ich einige Threads auf, die auf den obigen Einstiegspunkt abzielen, und sie erstellen und löschen zufällig Entitäten. Das funktioniert fast so, wie es sollte.

Das Problem ist, dass von Zeit zu Zeit einer der Threads einen bekommtEXC_BAD_ACCESS beim anrufenobj.<field> = <value>;. Mir ist nicht klar, woran es liegt, denn wenn ich druckeobj im debugger sieht alles gut aus. Vorschläge, wo das Problem liegen könnte (abgesehen von der Tatsache, dass Apple von einer Unterklasse von NSManagedObjectContext abrät) und wie man es repariert?

P.S. Ich kenne GCD undNSOperationQueue und andere Techniken, die typischerweise verwendet werden, um dieses Problem zu "lösen". Keiner von denen bietet an, was ich will. Was ich suche, ist eineNSManagedObjectContext Dies kann frei, sicher und direkt von einer beliebigen Anzahl von Threads verwendet werden, um den Anwendungsstatus anzuzeigen und zu ändern, ohne dass eine externe Synchronisierung erforderlich ist.

Antworten auf die Frage(3)

Ihre Antwort auf die Frage