崩溃:NSInternalInconsistencyException - 无效的 rowCache 行为 nil
Crash: NSInternalInconsistencyException - Invalid rowCache row is nil
我在 iOS 8.1.
上遇到 CoreData 并发问题
我得到以下崩溃堆栈跟踪:
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 条记录并每 100 条保存一次。
关于我的 CoreData 设置的几点说明:
- 我正在使用 "Stack #3" from this blog post
- 我所有的托管对象 updating/saving 代码都是在
performBlock:
上完成的
- 崩溃都在后台上下文
发生所有这些保存的代码具有以下基本模式:
[backgroundContext performBlock:^{
...
NSArray *result = [backgroundContext fetch...];
...
if ([backgroundContext save:&error]) { // <-- App is crashing here
}
}];
据我所知,backgroundContext
后面的 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
您认为这可能不是并发问题是正确的。当迁移未正确完成时,删除和安装应用程序通常会修复核心数据错误。
如果您发布了应用程序,更改了核心数据模型,然后发布了更新(没有迁移),这可能是您崩溃的根源。
按照本教程进行操作,看看它是否能解决您的问题。 http://www.raywenderlich.com/27657/how-to-perform-a-lightweight-core-data-migration
@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];
}];
}
这里有一些参考 links:
- Core Data from Scratch: Concurrency 策略 1:通知 您已经实施了。因此,请在您的场景中尝试此解决方案 策略 2:Parent/Child 托管对象上下文
- iOS 8: Core Data and Batch Updates
解决方案 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:&error])
{
// handle error
}
// save parent to disk asynchronously
[mainMOC performBlock:^{
NSError *error;
if (![mainMOC save:&error])
{
// handle error
}
}];
}];
方案二:将backgroundContext的NSManagedObject的值刷新到mainContext,然后调用mainContext的processPendingChanges OR mergeChangesFromContextDidSaveNotification。
试试这个代码而不是上面的代码(backgroundContextDidSave 方法):
- (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];
}];
}
很好的帮助-完整 link: CORE DATA with multiple managed object contexts
我 运行 遇到了类似的崩溃,但不一样,但我也使用了 Stack #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 块中,如果抛出异常,则在您的两个上下文中调用 reset。这将清除所有可能导致错误的行缓存。
我在 iOS 8.1.
上遇到 CoreData 并发问题我得到以下崩溃堆栈跟踪:
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 条记录并每 100 条保存一次。
关于我的 CoreData 设置的几点说明:
- 我正在使用 "Stack #3" from this blog post
- 我所有的托管对象 updating/saving 代码都是在
performBlock:
上完成的
- 崩溃都在后台上下文
发生所有这些保存的代码具有以下基本模式:
[backgroundContext performBlock:^{
...
NSArray *result = [backgroundContext fetch...];
...
if ([backgroundContext save:&error]) { // <-- App is crashing here
}
}];
据我所知,backgroundContext
后面的 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
您认为这可能不是并发问题是正确的。当迁移未正确完成时,删除和安装应用程序通常会修复核心数据错误。
如果您发布了应用程序,更改了核心数据模型,然后发布了更新(没有迁移),这可能是您崩溃的根源。
按照本教程进行操作,看看它是否能解决您的问题。 http://www.raywenderlich.com/27657/how-to-perform-a-lightweight-core-data-migration
@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];
}];
}
这里有一些参考 links:
- Core Data from Scratch: Concurrency 策略 1:通知 您已经实施了。因此,请在您的场景中尝试此解决方案 策略 2:Parent/Child 托管对象上下文
- iOS 8: Core Data and Batch Updates
解决方案 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:&error])
{
// handle error
}
// save parent to disk asynchronously
[mainMOC performBlock:^{
NSError *error;
if (![mainMOC save:&error])
{
// handle error
}
}];
}];
方案二:将backgroundContext的NSManagedObject的值刷新到mainContext,然后调用mainContext的processPendingChanges OR mergeChangesFromContextDidSaveNotification。 试试这个代码而不是上面的代码(backgroundContextDidSave 方法):
- (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];
}];
}
很好的帮助-完整 link: CORE DATA with multiple managed object contexts
我 运行 遇到了类似的崩溃,但不一样,但我也使用了 Stack #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 块中,如果抛出异常,则在您的两个上下文中调用 reset。这将清除所有可能导致错误的行缓存。