Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Data Save Error on iOS 10: Unresolved error Error Domain=NSCocoaErrorDomain Code=133020

I've been testing the new core data stack in iOS 10. My test app parses JSON data into core data and I am trying to make this happen while the user has access to the UI.

I am using the default core data stack and using a background context.

In AppDelegate.m:

- (NSPersistentContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"CoreDataTestingMDC"];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                }
            }];
        }
    }
    _persistentContainer.viewContext.automaticallyMergesChangesFromParent = YES;
    return _persistentContainer;
}

I have a simple master-detail UI that shows the core data entities in the master view controller and detailed attributes in the detail view. If the user does not scroll the master view, everything works fine. If the user scrolls, I usually get this error on save:

Unresolved error Error Domain=NSCocoaErrorDomain Code=133020 "(null)" UserInfo={conflictList=(
"NSMergeConflict (0x600000667c00) for NSManagedObject (0x610000096490) with objectID '0xd000000000440000 <x-coredata://CFF27A51-8F9E-4898-A4EA-CD85C0AFF300/ContentItem/p17>' 
with oldVersion = 44 and newVersion = 45...

It goes on to list the conflicting items which have exactly the same properties.

Also in my AppDelegate, I added a simple convenience method to generate the background context:

- (NSManagedObjectContext *)createBackgroundContext {
    return [self.persistentContainer newBackgroundContext];
}

This is passed back to the AppDelegate for a save operation:

- (void)saveContext:(NSManagedObjectContext *) theContext {
    NSError *error = nil;
    if ([theContext hasChanges] && ![theContext save:&error]) {
        NSLog(@"Unresolved error %@, %@", error, error.userInfo);
        abort();
    }
}

The UI is running on the viewContext as expected. I have been very careful to use the background context for all JSON parser writing. No idea why this is crashing.

Update:

It appears that the error occurs whenever the app is run after an initial run. I can test it fine on a clean simulator or after deleting the app. It parses data into core data fine and will also update while the user is interacting with the app. On a second build and run, the app will crash with the above error.

like image 612
BBruce Avatar asked Feb 06 '23 08:02

BBruce


1 Answers

It looks to me that you are having multiple writes taking place concurrently. To solve this you need write to core data in a single synchronous way.

In your core-data manager create a NSOperationQueue

_persistentContainerQueue = [[NSOperationQueue alloc] init];
_persistentContainerQueue.maxConcurrentOperationCount = 1;

And do all writing using this queue:

- (void)enqueueCoreDataBlock:(void (^)(NSManagedObjectContext* context))block{
    void (^blockCopy)(NSManagedObjectContext*) = [block copy];

    [self.persistentContainerQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
        NSManagedObjectContext* context =  self.persistentContainer.newBackgroundContext;
        [context performBlockAndWait:^{
            blockCopy(context);
            [context save:NULL];  //Don't just pass NULL here. look at the error and log it to your analytics service
        }];
    }]];
}

When you call enqueueCoreDataBlock the block is enqueued to ensures that there are no merge conflicts. But if you write to the viewContext that would defeat this setup. Likewise you should treat any other contexts that you create (with newBackgroundContext or with performBackgroundTask) as readonly because they will also be outside of the writing queue.

At first I thought that NSPersistentContainer's performBackgroundTask had an internal queue, and initial testing supported that. After more testing I saw that it could also lead to merge conflicts.

like image 166
Jon Rose Avatar answered Feb 13 '23 05:02

Jon Rose