Core Data Parent/Child context save fail

筅森魡賤 提交于 2020-01-03 04:43:07

问题


I setup a background thread with the Parent/Child model. Essentially the context save is failing.

Here is my setup. In the AppDelegate i've setup the _managedObjectContext with the NSMainQueueConcurrencyType:

- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {

        _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];//[[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

In my data loading class I setup the parent/child mocs here to perform the work on the background thread:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{

    NSManagedObjectContext *mainMOC = self.managedObjectContext;
    NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];


    [moc setParentContext:mainMOC];
    [moc setUndoManager:nil];

When the json data has completed I attempt to peform a save operation with the following macro:

#define SAVE_MOC    { NSError *error; \
if (![moc save:&error]) { NSLog(@"Sub MOC Error"); } \
[mainMOC performBlock:^{  NSError *e = nil;  if (![mainMOC save:&e]) {    

NSLog(@"Main MOC Error %@",error.localizedDescription);}}];}

Also when i've completed the data load I jump back on the main thread like this:

dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"<---- complete CS sub moc! ---->");
    //this fires ok

});

So, from my SAVE_MOC macro i just get a simple error: Main MOC Error (null)

Let me know if I can provide more info. I'm very new to multi-threading and trying to get a better handle on this approach.

Thanks, Josh


回答1:


In my data loading class I setup the parent/child mocs here to perform the work on the background thread:

dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
    NSManagedObjectContext *mainMOC = self.managedObjectContext;
    NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];

You should not do that. Do this instead.

NSManagedObjectContext *mainMOC = self.managedObjectContext;
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

Make sure you access the MOC in a performBlock. For example,

[moc performBlock:^{
    // Anything at all involving this MOC or any of its objects
}];

When the json data has completed I attempt to peform a save operation with the following macro:

Consider saving with something like this. Your completion block will be called when the save has finished.

- (void)saveMOC:(NSManagedObjectContext*)moc
     completion:(void(^)(NSError *error))completion {
    [moc performBlock:^{
        NSError *error = nil;
        if ([moc save:&error]) {
            if (moc.parentContext) {
                return [self saveMOC:moc.parentContext completion:completion];
            }
        }
        if (completion) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(error);
            });
        }
    }];
}

[self saveMOC:moc completion:^(NSError *error) {
    // Completion handler is called from main-thread, after save has finished
    if (error) {
        // Handle error
    } else {
    }
}];

EDIT

This code will crash if moc.parentContext is main concurrency type. – Mundi

There is no inherent reason that the code I posted should cause a crash with a parent MOC of NSMainQueueConcurrencyType. It has supported being a parent context ever since parent/child was added to Core Data.

Maybe I was missing a typo, so I copy/paste saveMOC:completion: straight from this answer, and wrote the following test helper.

- (void)testWithChildConcurrencyType:(NSManagedObjectContextConcurrencyType)childConcurrencyType
               parentConcurrencyType:(NSManagedObjectContextConcurrencyType)parentConcurrencyType {
    NSAttributeDescription *attr = [[NSAttributeDescription alloc] init];
    attr.name = @"attribute";
    attr.attributeType = NSStringAttributeType;
    NSEntityDescription *entity = [[NSEntityDescription alloc] init];
    entity.name = @"Entity";
    entity.properties = @[attr];
    NSManagedObjectModel *model = [[NSManagedObjectModel alloc] init];
    model.entities = @[entity];

    NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    [psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:NULL];
    NSManagedObjectContext *parent = [[NSManagedObjectContext alloc] initWithConcurrencyType:parentConcurrencyType];
    parent.persistentStoreCoordinator = psc;

    NSManagedObjectContext *child = [[NSManagedObjectContext alloc] initWithConcurrencyType:childConcurrencyType];
    child.parentContext = parent;

    NSManagedObject *obj = [NSEntityDescription insertNewObjectForEntityForName:@"Entity" inManagedObjectContext:child];
    [obj setValue:@"value" forKey:@"attribute"];

    XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"save from %@ to %@ finished", concurrencyTypeString(childConcurrencyType), concurrencyTypeString(parentConcurrencyType)]];
    [self saveMOC:child completion:^(NSError *error) {
        // Verify data saved all the way to the PSC
        NSManagedObjectContext *localMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        localMoc.persistentStoreCoordinator = psc;
        NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:@"Entity"];
        XCTAssertEqualObjects(@"value", [[[localMoc executeFetchRequest:fr error:NULL] firstObject] valueForKey:@"attribute"]);
        [expectation fulfill];
    }];
    [self waitForExpectationsWithTimeout:10 handler:nil];
}

And then, I wrote a test for each possible parent/child relationship.

- (void)testThatDoingRecursiveSaveFromPrivateToPrivateWorks {
    [self testWithChildConcurrencyType:NSPrivateQueueConcurrencyType
                 parentConcurrencyType:NSPrivateQueueConcurrencyType];
}
- (void)testThatDoingRecursiveSaveFromPrivateToMainWorks {
    [self testWithChildConcurrencyType:NSPrivateQueueConcurrencyType
                 parentConcurrencyType:NSMainQueueConcurrencyType];
}
- (void)testThatDoingRecursiveSaveFromMainToPrivateWorks {
    [self testWithChildConcurrencyType:NSMainQueueConcurrencyType
                 parentConcurrencyType:NSPrivateQueueConcurrencyType];
}
- (void)testThatDoingRecursiveSaveFromMainToMainWorks {
    [self testWithChildConcurrencyType:NSMainQueueConcurrencyType
                 parentConcurrencyType:NSMainQueueConcurrencyType];
}

So, what am I missing?

As I write this, I am reminded of a 360iDev presentation where the presenter said that you can't call performBlock on a NSMainQueueConcurrencyType context. At the time, I thought he just misspoke, meaning confinement, but maybe there is some confusion in the community about this.

You can't call performBlock on a NSConfinementConcurrencyType MOC, but performBlock is fully supported for NSMainQueueConcurrencyType.



来源:https://stackoverflow.com/questions/33577064/core-data-parent-child-context-save-fail

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!