Core Data concurrency issue

I have a web service downloading data and I want to process and import it into core data in the background thread (to keep the UI running). The way I’ve set this up is described in this blog post under “Asynchronous saving” http://www.cocoanetics.com/2012/07/multi-context-coredata/

I initialise my writer context like in this method.

+ (HFCoreDataManager *)writerContext {
    static HFCoreDataManager *writerContext = nil;
    static dispatch_once_t onceToken;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    dispatch_sync(queue, ^{
        dispatch_once(&onceToken, ^{
            writerContext = [[HFCoreDataManager alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

            AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
            [writerContext setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
        });
    });

    return writerContext;
}

I initialise my main context in this method.

+ (HFCoreDataManager *)mainContext {
    static HFCoreDataManager *mainContext = nil;
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        mainContext = [[HFCoreDataManager alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

        mainContext.parentContext = [HFCoreDataManager writerContext];
    });

    return mainContext;
}

And I set up my temporary contexts using this method.

+ (HFCoreDataManager *)temporaryContext {
    __block HFCoreDataManager *temporaryContext;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    dispatch_sync(queue, ^{
        temporaryContext = [[HFCoreDataManager alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        temporaryContext.parentContext = [HFCoreDataManager mainContext];
    });

    return temporaryContext;
}

Now the problem I’m having is when I import the data and go to return it in the import method sometimes I get no results. Because of the apparent unpredictability of whether I’ll get results or not, it appears there is some race condition in play. This is the method which imports data, saves the contexts and returns the results.

- (void)retrieveSuggestedEvents:(GetEventsBlock)completion {
    [self retrieveCurrentUser:^(User *currentUser) {
        NSArray *storedEvents = [[HFCoreDataManager mainContext] retrieveStoredSuggestedEventsForUser:currentUser];

        if (storedEvents.count > 0) {
            NSLog(@"SUCCESS: Retrieved %d suggested events from Core Data.", storedEvents.count);
            if (completion) {
                completion(storedEvents, NO);
            }
        }

        [[HFNetworkManager shared] downloadSuggestedEvents:^(NSArray *events) {

            NSLog(@"%d", events.count);

            HFCoreDataManager *temporaryContext = [HFCoreDataManager temporaryContext];

            [temporaryContext performBlock:^{
                for (NSDictionary *objectDict in events) {
                    NSMutableDictionary *mutableObjectDict = [objectDict mutableCopy];

                    NSDictionary *locationDict = [mutableObjectDict objectForKey:@"location"];
                    [mutableObjectDict removeObjectForKey:@"location"];
                    NSDictionary *ownerDict = [mutableObjectDict objectForKey:@"owner"];
                    [mutableObjectDict removeObjectForKey:@"owner"];
                    NSArray *friendsArray = [mutableObjectDict objectForKey:@"friends_attending"];
                    [mutableObjectDict removeObjectForKey:@"friends_attending"];                
                    Event *event = [temporaryContext createOrUpdateObjectOfEntity:[Event class] withData:mutableObjectDict];

                    // set up owner relation
                    User *owner = [temporaryContext createOrUpdateObjectOfEntity:[User class] withData:ownerDict];
                    event.owner = owner;
                    NSMutableSet *ownedEvents = [NSMutableSet setWithSet:owner.owned_events];
                    if (![ownedEvents containsObject:event]) {
                        [ownedEvents addObject:event];
                        owner.owned_events = ownedEvents;
                    }

                    // set up location relation
                    Location *location = [temporaryContext createOrUpdateObjectOfEntity:[Location class] withData:locationDict];
                    event.location = location;
                    NSMutableSet *locationEvents = [NSMutableSet setWithSet:location.events];
                    if (![locationEvents containsObject:event]) {
                        [locationEvents addObject:event];
                        location.events = locationEvents;
                    }

                    // set up suggested event relation
                    NSDictionary *idDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:[HFCoreDataManager suggestedEventIdentifierFromUser:currentUser andEvent:event]] forKey:@"id"];
                    SuggestedEvent *suggestedEvent = [temporaryContext createOrUpdateObjectOfEntity:[SuggestedEvent class] withData:idDict];

                    suggestedEvent.event = event;
                    if (![event.suggested_users containsObject:suggestedEvent]) {
                        NSMutableSet *usersSet = [NSMutableSet setWithSet:event.suggested_users];
                        [usersSet addObject:suggestedEvent];
                        event.suggested_users = usersSet;
                    }

                    User *backgroundCurrentUser = [temporaryContext retrieveStoredCurrentUser];
                    suggestedEvent.user = backgroundCurrentUser;
                    if (![currentUser.suggested_events containsObject:suggestedEvent]) {
                        NSMutableSet *eventsSet = [NSMutableSet setWithSet:backgroundCurrentUser.suggested_events];
                        [eventsSet addObject:suggestedEvent];
                        backgroundCurrentUser.suggested_events = eventsSet;
                    }

                    NSMutableArray *friendsAttending = [NSMutableArray arrayWithCapacity:friendsArray.count];
                    for (NSString *friendID in friendsArray) {
                        User *friend = [temporaryContext createOrUpdateObjectOfEntity:[User class] withData:[NSDictionary dictionaryWithObject:friendID forKey:@"id"]];

                        if (![backgroundCurrentUser.friends containsObject:friend]) {
                            NSMutableSet *friendsSet = [NSMutableSet setWithSet:backgroundCurrentUser.friends];
                            [friendsSet addObject:friend];
                            backgroundCurrentUser.friends = friendsSet;
                        }

                        if (![friend.friends containsObject:backgroundCurrentUser]) {
                            NSMutableSet *friendsSet = [NSMutableSet setWithSet:friend.friends];
                            [friendsSet addObject:backgroundCurrentUser];
                            friend.friends = friendsSet;
                        }

                        [friendsAttending addObject:friend];
                    }
                    suggestedEvent.friends_attending = [NSSet setWithArray:friendsAttending];
                }

                [temporaryContext saveContext];

                __block NSArray *suggestedEvents;

                [[HFCoreDataManager mainContext] performBlockAndWait:^{
                    suggestedEvents = [[HFCoreDataManager mainContext] retrieveStoredSuggestedEventsForUser:currentUser];
                }];


                if (suggestedEvents.count == 0) {
                    NSLog(@"damn");
                }

                if (completion) {
                    NSLog(@"CALLING COMPLETION WITH %d EVENTS", suggestedEvents.count);
                    completion(suggestedEvents, YES);
                }
            }];
        } failure:^(NSError *error) {

        }];
    }];
}

Take a look towards the bottom where I call saveContext on temporaryContext.

The saveContext method looks like this

- (void)saveContext {
    NSError *error;

    [self save:&error];
    if (error) {
        NSLog(@"ERROR SAVING MANAGED OBJECT CONTEXT %@: %@", self, error);
    }

    if (self.parentContext) {
        HFCoreDataManager *parentContext = (HFCoreDataManager *)self.parentContext;

        [parentContext performBlockAndWait:^{
            [parentContext saveContext];
        }];
    }
}

I have no idea what would be causing this race condition so any insight would be helpful.


View original post at stackoverflow.com