使用mergeChangesFromContextDidSaveNotification时,CoreData无法解决问题

时间:2014-11-20 01:40:44

标签: ios core-data magicalrecord

我在MagicalRecord的这一行得到uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x9f481f0 <x-coredata://ABF084FE-4BF3-4FC3-918A-BFF043589B8A/Structure/p21316>'

[[self MR_defaultContext] mergeChangesFromContextDidSaveNotification:notification];

我已经调试了2天,我仍然不确定发生了什么。

我已将其缩小到我的导入代码部分,该部分删除了导入的所有对象(给定实体)(因此删除了旧对象)。它看起来像这样:

- (void)deleteNonImportedEntities
{
    NSString *entityName = [self.configuration entityName];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (myID IN %@)", self.resourceIds];

    [MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {
        [localContext MR_setWorkingName:@"deleteNonImportedEntities context"];
        [NSClassFromString(entityName) MR_deleteAllMatchingPredicate:predicate inContext:localContext];
    }];
}

非常直截了当。这个错误的问题是它只发生在10次左右。如果我在这里放太多日志,那么它发生的更少。但我一直在努力缩小范围。我更改了saveWithBlockAndWait方法,以显示为该上下文插入(i),更新(u)或删除(d)的对象数量。

+ (void) saveWithBlockAndWait:(void(^)(NSManagedObjectContext *localContext))block;
{
    NSManagedObjectContext *savingContext  = [NSManagedObjectContext MR_rootSavingContext];
    NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextWithParent:savingContext];

    [localContext performBlockAndWait:^{
        [localContext MR_setWorkingName:NSStringFromSelector(_cmd)];

        if (block) {
            block(localContext);
        }
        MRLogVerbose(@"Saving saveWithBlockAndWait. (i: %i, u: %i, d: %d)", [[localContext insertedObjects] count], [[localContext updatedObjects] count], [[localContext deletedObjects] count]);
        [localContext MR_saveWithOptions:MRSaveParentContexts|MRSaveSynchronously completion:nil];
        MRLogVerbose(@"Finished saveWithBlockAndWait");
    }];
}

我还更改了rootContextDidSave(因为那是发生异常的地方)所以它会提供应该从上面的保存发送的通知中的信息(一旦它上传根保存上下文)。

+ (void) rootContextDidSave:(NSNotification *)notification
{
    if ([notification object] != [self MR_rootSavingContext])
    {
        return;
    }

    if ([NSThread isMainThread] == NO)
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self rootContextDidSave:notification];
        });

        return;
    }

    int inserted = [[[notification userInfo] objectForKey:@"inserted"] count];
    int updated = [[[notification userInfo] objectForKey:@"updated"] count];
    int deleted = [[[notification userInfo] objectForKey:@"deleted"] count];
    MRLogVerbose(@"Merging changes from notification to the default context (NSManagedObjectContext+MagicalRecord.m) (i: %i, u: %i, d: %i)", inserted, updated, deleted);
    [[self MR_defaultContext] mergeChangesFromContextDidSaveNotification:notification];
}

当代码没有崩溃时,日志如下所示:

... [278:21433] Saving saveWithBlockAndWait. (i: 0, u: 0, d: 9)
... [278:21433] → Saving <NSManagedObjectContext (0x191f8b30): deleteNonImportedEntities context> on a background thread
... [278:21433] → Save Parents? YES
... [278:21433] → Save Synchronously? YES
... [278:21433] → Saving <NSManagedObjectContext (0x1558d6e0): MagicalRecord Root Saving Context> on a background thread
... [278:21433] → Save Parents? YES
... [278:21433] → Save Synchronously? YES
... [278:21187] Merging changes from notification to the default context (NSManagedObjectContext+MagicalRecord.m) (i: 0, u: 13, d: 9)
... [278:21433] → Finished saving: <NSManagedObjectContext (0x1558d6e0): MagicalRecord Root Saving Context> on a background thread
... [278:21433] Finished saveWithBlockAndWait

我不确定为什么更新号会随着更改上下文而增加。

以下是终止时:

... [284:22234] Saving saveWithBlockAndWait. (i: 0, u: 0, d: 8)
... [284:22234] → Saving <NSManagedObjectContext (0xa062210): deleteNonImportedEntities context> on a background thread
... [284:22234] → Save Parents? YES
... [284:22234] → Save Synchronously? YES
... [284:22234] → Saving <NSManagedObjectContext (0x17df7b60): MagicalRecord Root Saving Context> on a background thread
... [284:22234] → Save Parents? YES
... [284:22234] → Save Synchronously? YES
... [284:22234] → Finished saving: <NSManagedObjectContext (0x17df7b60): MagicalRecord Root Saving Context> on a background thread
... [284:22234] Finished saveWithBlockAndWait
All Exceptions - Breakpoint
... [284:22200] *** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x9f481f0 <x-coredata://ABF084FE-4BF3-4FC3-918A-BFF043589B8A/Structure/p21316>''
*** First throw call stack:
(0x2ae8a49f 0x389bec8b 0x2aba47dd 0x2aba3bd1 0x2aba3a35 0x2abb261d 0x2bb118c9 0x2bb1148b 0x2bb11249 0x2bb11001 0x2abb8ac5 0x2abb7cc1 0x2ac8b103 0x2ac187ad 0x2ac1894f 0x2abb7b5d 0x2ae42c61 0x2ad9e6d5 0x2bad0189 0x2abb7acf 0x2ac18433 0x2ac1864d 0x9fca3 0x9fcfb 0xc1f9db 0xc1f9c7 0xc233ed 0x2ae503b1 0x2ae4eab1 0x2ad9c3c1 0x2ad9c1d3 0x321310a9 0x2e3abfa1 0x7e821 0x38f3eaaf)
libc++abi.dylib: terminating with uncaught exception of type _NSCoreDataException

如何正确调试此问题?看起来像在调用delete方法之前发生的导入工作正常并且会合并到默认上下文中。为什么插入/更新/删除的内容会随着通知的发送而发生变化?

我没有观察到要更改此数据的任何观点。我已经检查了我拥有的其他NSFetchedResultsController,我也不相信他们被解雇了(我也在那里得到了日志声明)。

更新:查看通知,因为它已发送到链。首次调用该方法时(并且它不在主线程中),只删除并更新了一些方法。但是当它到达主线程时,它只是一个插入的巨大列表。第一个通知中删除的那些在第二个通知中是故障的。现在只是想弄清楚它们与众不同的原因。

2 个答案:

答案 0 :(得分:2)

我已经诊断出这个问题,这与竞争状况有关。

首先在MagicalRecord上留下一点背景。它使用“保存”上下文,其他所有上下文都用作父项。 “默认”上下文是用于UI的上下文,因此是正在侦听通知的上下文,因此它可以合并更改。当您创建另一个上下文(或使用MagicalRecord的保存块)时,它将“保存”上下文设置为父节点。

问题是我的导入类中有两个主要方法。一个用于根据JSON数据(已保存到文件)导入所有对象,另一个用于删除不仅导入的对象。以下是每个的基本概念:

- (void)importEntities
{
    ...
    [saveResources readJSONResponsesFromDisk:^(NSDictionary *response) {
        [MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {
            NSArray *resources = [response objectForKey:JSONkey];

            NSArray *array = [NSClassFromString(entityName) MR_importFromArray:resources inContext:localContext];

            [weakSelf.resourceIds addObjectsFromArray:[array valueForKey:@"myID"]];
        }];
    }];
}

- (void)deleteNonImportedEntities
{
    ...
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (myID IN %@)", self.resourceIds];

    [MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {
        [NSClassFromString(entityName) MR_deleteAllMatchingPredicate:predicate inContext:localContext];
    }];
}

现在我每次使用saveWithBlockAndWait的最初目的是因为内存问题。它循环的每个文件都可以包含500个实体。由于MagicalRecord处理导入的方式,每个都可以创建多个其他对象。因此,我希望尽可能频繁地从内存中清除数据(并保存到磁盘),这样就不会产生过多的数据。

在了解问题的核心之前,需要了解数据在CoreData中的保存方式。由于saveWithBlockAndWait的上下文设置具有Saving上下文的父级,因此在保存时,其更改会自动合并到Saving上下文中。然后,Saving saving上下文发送其NSManagedObjectContextDidSaveNotification(子上下文也发送它,但默认上下文忽略它)。但是,在默认上下文合并从保存上下文发送的更改之前,运行并保存deleteNonImportedEntities。因此,它会在默认上下文之前保存到Saving上下文(大约15次中的1次)。因此,当默认上下文尝试合并这些更改时,通知指向的某些对象不会出现故障(它们已在“保存”上下文中删除)。因此它失败了。

现在我知道了,我已经取出了saveWithBlockAndWait次来电,并且只为整个班级使用了一个localContext。我猜我将不得不回到使用自动释放池并定期保存。

更新:实际上,这会导致同样的问题。所以我试图想出另一个解决方案。

更新2:我最终接受了Aaron的建议,并将其转移到了自己的操作中。我还改变了优先级,以帮助将它们分开一点。

答案 1 :(得分:0)

enter image description here

我也在使用MagicalRecord,最近遇到了类似的问题。

  

CoreData:警告:NSManagedObjectContext委托覆盖错误   处理行为以静默方式删除具有ID的对象   “0xd0000000071c0006   &LT; x-coredata:// F5A1A83A-F160-475D-B313-EC68CDADCEF8 / MyModel / p455&gt;'   并用nil / 0替换所有属性值而不是抛出。

我的核心数据堆栈有点复杂,根是NSPersistentStoreCoordinator。右侧是Application的主要嵌套堆栈。

  • MR_rootSavingContext,后台上下文,用于写入磁盘
  • MR_defaultContext,用于主线程UI,例如NSFetchedResultsController
  • MyWorkerContext,背景上下文,用于Core Data从API逻辑编写的入口点

BackgroundContext1或BackgroundContext2是我实现一些大规模数据操作的计划,这些操作将在后台线程上进行舍入。

请注意,显示BackgroundContext1的红线会侦听MyWorkerContext并合并更改,这将导致错误。原因是当BackgroundContext1保存并发布有关更改的通知时,更改尚未传播到磁盘。没有适用于BackgroundContext1的数据,因此无法实现错误。

我的建议是 NSManagedObjectContext只会合并来自同一级别的其他NSManagedObjectContexts 或父级的更改。例如。以下两种情况都可以:

  • BackgroundContext1侦听并合并来自MR_rootSavingContext
  • 的更改
  • BackgroundContext1侦听并合并MyWorkerContext的更改