核心数据父/子上下文保存失败

Posted

技术标签:

【中文标题】核心数据父/子上下文保存失败【英文标题】:Core Data Parent/Child context save fail 【发布时间】:2015-11-06 23:16:36 【问题描述】:

我使用父/子模型设置了一个后台线程。本质上,上下文保存失败了。

这是我的设置。在 AppDelegate 中,我使用 NSMainQueueConcurrencyType 设置了 _managedObjectContext:

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

在我的数据加载类中,我在这里设置了父/子 mocs 以在后台线程上执行工作:

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

当 json 数据完成后,我尝试使用以下宏执行保存操作:

#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);];

当我完成数据加载后,我会像这样跳回主线程:

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

);

所以,从我的 SAVE_MOC 宏中,我得到了一个简单的错误: 主要 MOC 错误(空)

如果我能提供更多信息,请告诉我。我对多线程非常陌生,并试图更好地处理这种方法。

谢谢, 乔什

【问题讨论】:

你记录了错误的错误,参数应该是e,而不是error。执行块是异步的,能保证执行顺序吗?此外,不推荐使用限制,因此您应该对除主线程上的主上下文之外的所有内容使用执行块。 【参考方案1】:

在我的数据加载类中,我在这里设置了父/子 mocs 来执行 后台线程的工作:

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

你不应该那样做。改为这样做。

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

确保您通过performBlock 访问 MOC。例如,

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

当 json 数据完成后,我尝试执行保存操作 使用以下宏:

考虑用这样的方式保存。保存完成后将调用您的完成块。

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

编辑

如果 moc.parentContext 是主要并发类型,此代码将崩溃。 – 世界

我发布的代码没有内在的原因会导致父 MOC 为 NSMainQueueConcurrencyType 的崩溃。自从父/子添加到 Core Data 以来,它就支持成为父上下文。

也许我错过了一个错字,所以我直接从这个答案复制/粘贴saveMOC:completion:,并编写了以下测试助手。

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

然后,我为每个可能的父/子关系编写了一个测试。

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

那么,我错过了什么?

在我写这篇文章的时候,我想起了一个 360iDev 演示文稿,演示者说你不能在 NSMainQueueConcurrencyType 上下文中调用 performBlock。当时,我以为他只是说错话了,意思是坐月子,但也许社区对此有些困惑。

您不能在NSConfinementConcurrencyType MOC 上调用performBlock,但NSMainQueueConcurrencyType 完全支持performBlock

【讨论】:

所以,我不应该使用“dispatch_async”,因为 moc 上的所有工作都在发生......我应该使用 NSPrivateQueueConcurrencyType 而不是限制类型。感谢您的反馈。我绝对愿意尝试一种新方法。 dispatch_async 用于其他内容。永远不要使用约束并发。始终使用performBlock 进行任何核心数据访问。 如果moc.parentContext是主要并发类型,这段代码会崩溃。 @Mundi - 你为什么这么说?我又看了一遍,也许我漏掉了一个错字,但我看不出它应该崩溃的明显原因。 嗨,是的,我认为这是一个错字。感谢您的丰富回复:-)。

以上是关于核心数据父/子上下文保存失败的主要内容,如果未能解决你的问题,请参考以下文章

核心数据:更新子上下文

与核心数据的关系错误

核心数据:父/子托管对象上下文是不是适合更新一组多个对象?

将子对象添加到表中 当父对象保存到上下文中时

子父上下文保存冲突

保存后在子上下文中创建的新对象在父上下文中不存在