문제

I have an NSOperationQueue that has some NSBlockOperations added to it, among which are blockOperations A and B. The NSOperationQueue has a maxConcurrencyOperationCount of 1.

blockOperation B, is dependant on A being finished. In each of the blockOperations I am calling a method, which in turn calls another method, that initialises a new NSManagedObjectContext (with the persistentStoreCoordinator from a singleton), that I use to create and add objects to a Core Data database. The code invoked by the aforementioned second method call in A and B looks like this (it varies slightly for for each of them):

NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
managedObjectContext.persistentStoreCoordinator = [[CoreDataController sharedCoreDataController] persistantStoreCoordinator];

for (NSDictionary *articleDictionary in array) {

    if (![Article articleExistsWithIDInDictionary:articleDictionary inContext:managedObjectContext]) {

        [Article articleFromDictionary:articleDictionary inContext:managedObjectContext];
    }
}

[[CoreDataController sharedCoreDataController] saveContext:managedObjectContext];
// method ends.

The saveContext: code looks like this:

NSError *error = nil;
if (managedObjectContext != nil) {
    if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {

        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    }
}

Having spent a lot of time reading Apples Docs about Core Data Concurrency, NSOperation, etc., I'm still unsure if what I'm doing with NSManagedObjectContext is thread-safe, and generally considered to be OK? Some clarification and/or indication of that I should be doing differently would be much appreciated. If you need to see any more code, please ask.

Thanks in advance.

도움이 되었습니까?

해결책

What you are doing is NOT thread safe.

If you decide to create a context per operation, you better use the confinement concurrency type:

context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];

this way you don't need to change anything in your current code.

if you want to use the context with NSPrivateQueueConcurrencyType you must access objects within that context by the performBlock: or performBlockAndWait::

[managedObjectContext performBlockAndWait:^{//wait is used as to not end the operation before this code is executed
    for (NSDictionary *articleDictionary in array) {
        if (![Article articleExistsWithIDInDictionary:articleDictionary
                                            inContext:managedObjectContext]) 
        {
            [Article articleFromDictionary:articleDictionary 
                                 inContext:managedObjectContext];
        }
    }
}];

I would probably go with my first solution in your case.

all that said, you could simply use a "private queue" context as a serial queue (as long as you add block operation to it in the order you need them to execute).
A context performBlock: method will queue the block and execute it serially in regard to other blocks added that context for execution in the background:

//add this to your CoreDataController
context = [[CoreDataController sharedCoreDataController] serialExecutionBGContext];
[context performBlock:^{ //your block operation code1}];
[context performBlock:^{ //your block operation code2}];

this will perform code1 and code2 in the background serially.

In this manner, you save the overhead of allocating a new context, and might benefit from caching done by this context.
You might want to reset this context every now and then so it will not get bloated with fetched objects.

다른 팁

The concern with the context is that it be accessed only within a single thread. Setting the MaxConcurrencyOperationCount does not guarantee that. Another approach is to make the context a "thread" variable, storing a context in each thread dictionary where it is used.

Ex:

+ (NSManagedObjectContext*)managedObjectContext
{
    NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
    NSManagedObjectContext *context = [threadDictionary valueForKey:@"QpyManagedObjectContext"];
    if (context == nil) {
        @autoreleasepool {
            context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
            [context setStalenessInterval: 10.0];
            [context setMergePolicy:[[NSMergePolicy alloc]initWithMergeType:NSOverwriteMergePolicyType]];

            NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[Qpyd managedObjectModel]];
            [context setPersistentStoreCoordinator:coordinator];

            NSString *STORE_TYPE = NSSQLiteStoreType;

            NSString *path = [[NSProcessInfo processInfo] arguments][0];
            path = [path stringByDeletingPathExtension];
            NSURL *url = [NSURL fileURLWithPath:[path stringByAppendingPathExtension:@"sqlite"]];

            NSError *error;
            NSPersistentStore *newStore = [coordinator addPersistentStoreWithType:STORE_TYPE configuration:nil URL:url options:nil error:&error];

            if (newStore == nil) {
                NSLog(@"Store Configuration Failure %@", ([error localizedDescription] != nil) ? [error localizedDescription] : @"Unknown Error");
            }
            [threadDictionary setObject:context forKey:@"QpyManagedObjectContext"];
        }
    }
    return context;
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top