2015-02-11 82 views
10

我在iOS 8.1上遇到CoreData并发问题。崩溃:NSInternalInconsistencyException - 无效的rowCache行是零

我得到以下堆栈跟踪崩溃:

NSInternalInconsistencyException - Invalid rowCache row is nil

0  CoreFoundation      0x0000000183b6659c __exceptionPreprocess + 132 
1  libobjc.A.dylib      0x00000001942640e4 objc_exception_throw + 56 
2  CoreData        0x000000018385b8b8 -[NSSQLCore _newRowCacheRowForToManyUpdatesForRelationship:rowCacheOriginal:originalSnapshot:value:added:deleted:sourceRowPK:properties:sourceObject:newIndexes:reorderedIndexes:] + 6668 
3  CoreData        0x00000001838fbea0 -[NSSQLCore recordToManyChangesForObject:inRow:usingTimestamp:inserted:] + 2604 
4  CoreData        0x0000000183857638 -[NSSQLCore prepareForSave:] + 1052 
5  CoreData        0x00000001838569b4 -[NSSQLCore saveChanges:] + 520 
6  CoreData        0x000000018381f078 -[NSSQLCore executeRequest:withContext:error:] + 716 
7  CoreData        0x00000001838e6254 __65-[NSPersistentStoreCoordinator executeRequest:withContext:error:]_block_invoke + 4048 
8  CoreData        0x00000001838ed654 gutsOfBlockToNSPersistentStoreCoordinatorPerform + 176 
9  libdispatch.dylib      0x00000001948a936c _dispatch_client_callout + 12 
10 libdispatch.dylib      0x00000001948b26e8 _dispatch_barrier_sync_f_invoke + 72 
11 CoreData        0x00000001838e0cb4 _perform + 176 
12 CoreData        0x000000018381ec34 -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 296 
13 CoreData        0x0000000183845400 -[NSManagedObjectContext save:] + 1280 

这崩溃发生在几个地方(约20),但在我的数据导入功能,其中最突出的进口记录1000和保存有关我的CoreData设置每100

的几个注意事项:

,所有这些都是节约的代码发生具有以下基本模式:

[backgroundContext performBlock:^{ 
    ... 
    NSArray *result = [backgroundContext fetch...]; 
    ... 

    if ([backgroundContext save:&error]) { // <-- App is crashing here 

    } 
}]; 

据我了解,不应该有与backgroundContext而T3背后的NSPersistentStore任何并发问题帽子是坠机告诉我的。

此外,这只发生在我的用户群的0.02%以内。这是非常罕见的(我无法重现),但用户是而不是能够从此恢复而不删除并重新安装应用程序。它会一直打开并为他们崩溃。

请注意,这仅适用于64位iOS 8.1.X - iOS 7.X和8.0.X不会显示此行为,并且在任何比iPhone 5s更旧的东西上我都看不到它。


说明:删除持久性存储可以为所有用户解决此问题。这似乎表明它不是并发问题。


创建商店&上下文。

[_persistenStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType 
            configuration:nil 
               URL:[DatabaseManager storeURL] 
              options:@{NSMigratePersistentStoresAutomaticallyOption:@YES, 
                NSInferMappingModelAutomaticallyOption:@YES} 
              error:&error]; 

创建上下文

_backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
[_backgroundContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy]; 
[_backgroundContext setPersistentStoreCoordinator:_persistenStoreCoordinator]; 
if ([_backgroundContext respondsToSelector:@selector(setName:)]) { 
    [_backgroundContext setName:@"DatabaseManager.BackgroundQueue"]; 
} 

_mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 
[_mainContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy]; 
[_mainContext setPersistentStoreCoordinator:_persistenStoreCoordinator]; 
if ([_mainContext respondsToSelector:@selector(setName:)]) { 
    [_mainContext setName:@"DatabaseManager.MainQueue"]; 
} 

[[NSNotificationCenter defaultCenter] addObserver:self 
             selector:@selector(mainContextDidSave:) 
              name:NSManagedObjectContextDidSaveNotification 
              object:_mainContext]; 

[[NSNotificationCenter defaultCenter] addObserver:self 
             selector:@selector(backgroundContextDidSave:) 
              name:NSManagedObjectContextDidSaveNotification 
              object:_backgroundContext]; 

听力变化

- (void) mainContextDidSave:(NSNotification *)notification 
{ 
    [_backgroundContext performBlock:^{ 
     [self->_backgroundContext mergeChangesFromContextDidSaveNotification:notification]; 
    }]; 
} 

- (void) backgroundContextDidSave:(NSNotification*)notification 
{ 
    [_mainContext performBlock:^{ 
     NSArray* updated = [notification.userInfo valueForKey:NSUpdatedObjectsKey]; 

     // Fault all objects that will be updated. 
     for (NSManagedObject* obj in updated) { 
      NSManagedObject* mainThreadObject = [self->_mainContext objectWithID:obj.objectID]; 
      [mainThreadObject willAccessValueForKey:nil]; 
     } 

     [self->_mainContext mergeChangesFromContextDidSaveNotification:notification]; 
    }]; 
} 

如何代码在每个上下文中执行。我传递一个块到这里:

- (void)executeBackgroundOperation:(void (^)(NSManagedObjectContext *))operation { 

    NSAssert(operation, @"No Background operation to perform"); 

    [_backgroundContext performBlock:^{ 
     operation(self->_backgroundContext); 
    }]; 
} 

- (void)executeMainThreadOperation:(void (^)(NSManagedObjectContext *))operation { 
    NSAssert(operation, @"No Main Thread operation to perform"); 

    [_mainContext performBlock:^{ 
     operation(self->_mainContext); 
    }]; 
} 

的崩溃报告的唯一其他线程,在他们有一个lock是这个样子两个JavaScript线程:

0 libsystem_kernel.dylib 0x3a77cb38 __psynch_cvwait + 24 
1 libsystem_pthread.dylib 0x3a7fa2dd pthread_cond_wait + 38 
2 libc++.1.dylib 0x39a11e91 _ZNSt3__118condition_variable4waitERNS_11unique_lockINS_5mutexEEE + 34 
3 JavaScriptCore 0x2dcd4cb5 _ZN3JSC8GCThread16waitForNextPhaseEv + 102 
4 JavaScriptCore 0x2dcd4d19 _ZN3JSC8GCThread12gcThreadMainEv + 50 
5 JavaScriptCore 0x2db09597 _ZN3WTFL19wtfThreadEntryPointEPv + 12 
6 libsystem_pthread.dylib 0x3a7f9e93 _pthread_body + 136 
7 libsystem_pthread.dylib 0x3a7f9e07 _pthread_start + 116 
8 libsystem_pthread.dylib 0x3a7f7b90 thread_start + 6 
+0

嘿斯蒂芬,当多个上下文指向单个nsmanagedobject并在同一时间修改关系并尝试保存上下文时,会发生此崩溃。确保在修改表格及其数据时调用保存。 – Punita 2015-02-18 12:02:10

+0

@Punita我只有一个上下文保存到持久存储区,所有'save:'调用都在'performBlock:'上进行,所以除非上下文并行执行块,否则我不会看到这会发生。 – 2015-02-18 14:54:02

+0

正如你所写**更新/保存代码是在performBlock **和**上完成的**崩溃全部在后台上下文**上。所以我的理解是,你在后台上下文中插入数据,而不是在主线程的performBlock中保存托管对象。如果我是正确的,那么在执行保存时,它会通知NSManagedObjectContextDidSaveNotification背景的上下文,并且在此通知中,您将** info字典包含具有被插入,删除和更新的托管对象的数组**如Apple文档中所述:与核心数据并发 – Punita 2015-02-19 07:51:20

回答

3

你是正确的认为这可能不是一个并发问题。当迁移没有正确完成时,删除和安装应用程序确实会修复核心数据错误。

如果您发布应用程序,更改了核心数据模型,然后发布了更新(无需迁移),则可能是您崩溃的原因。

请按照本教程,看看它是否修复您的问题。 http://www.raywenderlich.com/27657/how-to-perform-a-lightweight-core-data-migration

+0

这是我们尚未使用轻量级coredata迁移的第一个版本。实际上,我们必须为其中一个类创建映射模型和迁移策略,但它不是应用程序崩溃时正在更新的类。 – 2015-02-24 01:40:02

+0

但是你可能已经搞砸了。无论哪种方式,您的问题很可能是由于错误的迁移。在没有轻量级迁移的情况下进行更新之后,发生崩溃并不是巧合。 – 2015-02-24 01:56:15

+0

也许回到那个版本,看看迁移是否解决了这个问题。但我知道这很难复制。也许尝试通过测试飞行或其他方式让受影响的设备成为固定版本。您也可以尝试在设备上加载早期版本,更新到新版本(不删除旧版本),然后尝试查看是否可以中断应用。 – 2015-02-24 01:57:18

0

@StephenFurlani,

我不是核心数据的专家。但我喜欢解决问题。我认为应用越来越崩溃,合并ManagedObject从backgroundContext到MainContext(如你提到你的代码):

- (void) backgroundContextDidSave:(NSNotification*)notification 
{ 
    [_mainContext performBlock:^{ 
     NSArray* updated = [notification.userInfo valueForKey:NSUpdatedObjectsKey]; 

     // Fault all objects that will be updated. 
     for (NSManagedObject* obj in updated) { 
      NSManagedObject* mainThreadObject = [self->_mainContext objectWithID:obj.objectID]; 
      [mainThreadObject willAccessValueForKey:nil]; 
     } 

     [self->_mainContext mergeChangesFromContextDidSaveNotification:notification]; 
    }]; 
} 

这里有一些参考链接:

解决方案1:试试这个下面的代码:

NSMangedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
temporaryContext.parentContext = mainMOC; 

[temporaryContext performBlock:^{ 
    // do something that takes some time asynchronously using the temp context 

    // push to parent 
    NSError *error; 
    if (![temporaryContext save:&amp;error]) 
    { 
     // handle error 
    } 

    // save parent to disk asynchronously 
    [mainMOC performBlock:^{ 
     NSError *error; 
     if (![mainMOC save:&amp;error]) 
     { 
     // handle error 
     } 
    }]; 
}]; 

解决方案2:从backgroundContext的NSManagedObject刷新到mainContext的值,然后调用mainContext的processPendingChanges或mergeChangesFromContextDidSaveNotification。

- (void) backgroundContextDidSave:(NSNotification*)notification 
    { 
     [_mainContext performBlock:^{ 
      NSArray* updated = [notification.userInfo valueForKey:NSUpdatedObjectsKey]; 

      // Fault all objects that will be updated. 
      for (NSManagedObject* obj in updated) { 
       NSManagedObject* mainThreadObject = [self->_mainContext objectWithID:obj.objectID]; 
       if (mainThreadObject) { 
        [self->_mainContext refreshObject: mainThreadObject mergeChanges:NO]; 
       } 
      } 

      [self->_mainContext processPendingChanges]; 
     }]; 
    } 

很好的帮助,完全链接:你的代码(backgroundContextDidSave法)以上 试试这个代码,而不是CORE DATA with multiple managed object contexts

0

我遇到类似的崩溃,不一样的,但是我用的是一个堆栈#3设置也是如此。在从后台保存期间,崩溃发生在您的同一行上。我发现的原因是,当CoreData操作启动多个线程时,线程按错误的顺序完成,但有时只是完成。对我们来说,这是一个问题,因为我们在模型中有一些依赖操作和对象关系。

我通过管理我自己的线程和我自己的队列进行后台下载来修复它。这样我可以处理订单,并且始终知道我有一个FIFO类型的流程。这也解决了保存时的崩溃问题。

我在下面添加了一些代码,如果你想试试看。

- (void) keepAlive 
{ 
    [self performSelector:@selector(keepAlive) withObject:nil afterDelay:60]; 
} 

- (void)backgroundThreadMain 
{ 
    // Add selector to prevent CFRunLoopRunInMode from returning immediately 
    [self performSelector:@selector(keepAlive) withObject:nil afterDelay:60]; 
    BOOL done = NO; 

    do 
    { 
     // Start the run loop but return after each source is handled. 
     SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES); 

     // If a source explicitly stopped the run loop, or if there are no 
     // sources or timers, go ahead and exit. 
     if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished)) 
     done = YES; 
    } 
    while (!done); 
} 

self.backgroundThread = [[NSThread alloc] initWithTarget:self selector:@selector(backgroundThreadMain) object:nil]; 
[self.backgroundThread start]; 

然后任何时候我需要运行一段代码我使用了该线程的静态引用。

[[CoreDataControl coreDataControlManager] performSelector:@selector(saveSite:) onThread:[CoreDataControl coreDataControlManager].backgroundThread withObject:site waitUntilDone:NO]; 

最坏的情况下,可以包裹保存在一个try catch块,如果有异常抛出两个您的通话环境复位。这将清除可能导致错误的任何行缓存。

相关问题