这次核心数据崩溃的可能原因是啥
Posted
技术标签:
【中文标题】这次核心数据崩溃的可能原因是啥【英文标题】:What's the possible cause of this Core Data crash这次核心数据崩溃的可能原因是什么 【发布时间】:2012-06-02 21:47:59 【问题描述】:首先,很抱歉我无法为这个问题找到更好的标题。
这是一个我无法重复自己但多次发生在用户身上的崩溃。我正在使用 HockeyApp(QuincyKit) 收集崩溃报告。所以无法调试这个问题,只能读取调用堆栈。但是读完之后我不知道出了什么问题。中间省略了几个不相关的函数调用。
对于 UI 部分,我使用NSFetchedResultsController
填充UITableView
。
对于核心数据部分,我使用 ios 5 新引入的父子 MOC:父 MOC 用于主队列,子 MOC 用于资源获取队列。我在处理其中获取的数据后保存子 MOC。一旦我保存子 MOC,Core Data 会自动将更改推送到父 MOC,FRC 应该通过其委托查看更改并更新表视图。然后我回调到主线程进行额外的 UI 更新。
具体来说,我可以理解[UITableView indexPathForRowAtPoint:]
是如何调用[NSFetchedResultsController objectAtIndexPath:]
的,但我不明白为什么它最终会调用我实体的initWithEntity:insertIntoManagedObjectContext:
以及为什么initWithEntity:insertIntoManagedObjectContext:
会导致未实现的错误。我认为[NSFetchedResultsController objectAtIndexPath:]
应该是fetch
,但这里是insert
。
Exception Type: SIGABRT
Exception Codes: #0 at 0x35e7e32c
Crashed Thread: 0
Application Specific Information:
*** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0xe62bdc0 <x-coredata://327F5799-CAB1-4086-8E75-0E87F0E19BF0/Status/p385>''
Last Exception Backtrace:
0 CoreFoundation 0x355c888f __exceptionPreprocess + 162
1 libobjc.A.dylib 0x3796f259 objc_exception_throw + 32
2 CoreData 0x35f124f3 _PFFaultHandlerLookupRow + 1098
3 CoreData 0x35f11d5b _PF_FulfillDeferredFault + 194
4 CoreData 0x35f11c0b _sharedIMPL_pvfk_core + 38
5 XXX 0x0008a5c3 -[Tweet initWithEntity:insertIntoManagedObjectContext:] (Tweet.m:125)
6 XXX 0x0003fd1b -[Status initWithEntity:insertIntoManagedObjectContext:] (Status.m:333)
7 CoreData 0x35f0eded -[NSManagedObject(_NSInternalMethods) _initWithEntity:withID:withHandler:withContext:] + 164
8 CoreData 0x35f0de07 -[NSManagedObjectContext(_NSInternalAdditions) _retainedObjectWithID:optionalHandler:withInlineStorage:] + 134
9 CoreData 0x35f6b6eb _PFRetainedObjectIDCore + 330
10 CoreData 0x35f67065 -[NSManagedObjectContext objectWithID:] + 88
11 CoreData 0x35f32c09 _faultBatchAtIndex + 1352
12 CoreData 0x35f31e73 -[_PFBatchFaultingArray objectAtIndex:] + 42
13 CoreData 0x35f30a87 -[_PFMutableProxyArray objectAtIndex:] + 82
14 CoreData 0x35fdd81d -[NSFetchedResultsController objectAtIndexPath:] + 204
16 XXX 0x0009ebab -[TweetsViewController tableView:heightForRowAtIndexPath:] (TweetsViewController.m:581)
17 UIKit 0x3304dab5 -[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 2548
18 UIKit 0x33067c3b -[UITableViewRowData rectForSection:] + 302
19 UIKit 0x33125981 -[UITableViewRowData indexPathsForRowsInRect:] + 136
20 UIKit 0x331258f1 -[UITableView indexPathsForRowsInRect:] + 52
21 UIKit 0x33125889 -[UITableView indexPathForRowAtPoint:] + 28
22 XXX 0x0009c193 -[TweetsViewController updateUI] (TweetsViewController.m:211)
25 XXX 0x00111a6f __block_global_1 (ResourceFetcher.m:68)
26 libdispatch.dylib 0x34458b87 _dispatch_barrier_sync_f_slow_invoke + 82
27 libdispatch.dylib 0x34457ee7 _dispatch_main_queue_callback_4CF$VARIANT$mp + 194
28 CoreFoundation 0x3559b2ad __CFRunLoopRun + 1268
29 CoreFoundation 0x3551e4a5 CFRunLoopRunSpecific + 300
30 CoreFoundation 0x3551e36d CFRunLoopRunInMode + 104
31 GraphicsServices 0x371ba439 GSEventRunModal + 136
32 UIKit 0x3302acd5 UIApplicationMain + 1080
33 XXX 0x000044ff main (main.m:16)
34 XXX 0x00003c50 start + 40
遇到此崩溃问题的用户通常会在崩溃前突然看到表格视图从充满单元格变为空。我认为这是一个重要的提示,但我想不出所有数据突然消失的任何原因——我没有移动或删除数据库文件的代码。
【问题讨论】:
最可能的原因是您有一组要用于填充表格视图的获取对象,并且您的商店在后台发生了变化(即您下载并用新数据替换了它,复制一个新的数据库文件覆盖旧的等。另一种可能性是您在不同的线程中使用相同的managedobjectcontext
。
@Rog 正如我在问题中所说,我使用NSFetchedResultsController
来填充我的UITableView
。我在 iOS 5 的父子模式的多线程中使用 MOC,我所有的数据最终都保存在主 MOC 中,所以我认为我的数据同步良好。仅供参考,问题中的回溯是针对主线程的。
【参考方案1】:
首先,在查找错误时,没有不相关的函数调用,因此您可能应该使用整个调用堆栈进行编辑...
看起来您正在请求一个对象(请参阅 objectWithID 调用),但是当托管对象上下文去获取它时,它找不到它。
这通常发生在您有一个单独的线程正在删除对象并且您没有正确管理更改时。
那么,您是否有任何其他线程正在更改您的核心数据存储的状态?如果是这样,那可能是你的罪魁祸首。
Core Data 并不难……但它确实有几条规则,如果你不遵守,你就会遇到麻烦。
首先,确保您没有从多个线程访问 MOC。
其次,如果多个线程正在更改底层存储,请确保您与所有 MOCS 正确同步 - 使用父/子上下文或查看 DidSave 通知。
编辑
你评论了:
是的,我也怀疑有一些正在删除的东西 问题。但首先,你知道为什么 initWithEntity:insertIntoManagedObjectContext: 这里调用了吗?
是的...它需要实例化对象。查看您的调用堆栈,您可以看到它...
您的 UI 正在尝试在主线程上更新...
22 XXX 0x0009c193 -[TweetsViewController updateUI] (TweetsViewController.m:211)
因此,UIKit 会做自己的事情,并向您的控制器发出委托回调...
16 XXX 0x0009ebab -[TweetsViewController tableView:heightForRowAtIndexPath:] (TweetsViewController.m:581)
这会导致您所说的 FRC 调用...
14 CoreData 0x35fdd81d -[NSFetchedResultsController objectAtIndexPath:] + 204
然后,需要该对象,因此将其取出...
10 CoreData 0x35f67065 -[NSManagedObjectContext objectWithID:] + 88
好吧,即使是 Core Data 也必须实例化对象。它没有一些神奇的界面。它调用用于创建托管对象的普通 API...显然您正在请求“状态”类型的对象
6 XXX 0x0003fd1b -[Status initWithEntity:insertIntoManagedObjectContext:] (Status.m:333)
反过来,这个对象需要一个“Tweet”类型的对象...
5 XXX 0x0008a5c3 -[Tweet initWithEntity:insertIntoManagedObjectContext:] (Tweet.m:125)
然后,当它必须实例化那个对象时......它失败了,因为它找不到它。因此,我再次打赌,您的同步中存在逻辑错误。您正在删除一个对象并且没有正确通知其他上下文,因此它们仍在引用已删除的对象......或者您正在引用一个没有真正保存的对象......或者您已经更改了关系,但没有正确通知其他情况。
无论哪种方式,我认为如果你跟踪你是如何更改数据库的,我认为这是一个不错的选择,你违反了一些在多线程中更新的标准协议。
事实上,你甚至可以看到你是如何到达那里的......
updateUI 正在从 GCD 调度队列中调用。最有可能的是,在 ResourceFetcher 中,您正在调用主线程。在该代码中,请确保您已正确保存上下文并发出所有通知,然后再要求主线程自行更新。
你最好从另一个 MOC 监听 DidSave 事件,然后在从那个上下文更新之后,调用你的 UI 来更新自己。
【讨论】:
我确信那些省略的函数在这里与讨论无关,因为它们都是我的应用程序中定义的连续函数。我在这里只保留了最深的一个。我认为这还可以,更清楚,不是吗?我在 iOS 5 的父子模式的多线程中使用 MOC,我所有的数据最终都保存在主 MOC 中,所以我认为我的数据同步良好。仅供参考,问题中的回溯是针对主线程的。 是的。如果没有更多信息,我敢打赌那是你的问题。您没有正确管理对存储的更改。一个线程正在更改底层存储(很可能是删除),而另一个线程期望它存在并且找不到它。另一个最好的选择是您可能从错误的线程/MOC 组合访问对象。 是的,我也怀疑存在一些删除使用中的问题。但首先,你知道这里为什么会调用initWithEntity:insertIntoManagedObjectContext:
吗?
感谢您的详细解答。但是,initWithEntity:insertIntoManagedObjectContext:
不会创建一个新对象,该对象稍后将作为新记录保存到数据库中吗?我问为什么要调用它,因为我认为这里 FRC 是在请求现有对象来填充表格视图,而不是创建新对象。
我正在使用父子 MOC:父 MOC 用于主队列,子 MOC 用于资源获取队列。我在处理其中获取的数据后保存子 MOC。一旦我保存子 MOC,Core Data 会自动将更改推送到父 MOC,FRC 应该通过其委托查看更改并更新表视图。然后我回调主线程以进行额外的 UI 更新。我看不出怎么会有任何 MOC 同步问题。我忽略了什么?【参考方案2】:
最后,我想我已经修复了崩溃,虽然我不确定这是我的错误还是 Core Data 的错误。
首先,它与MOC之间的同步无关。
其次,是的,initWithEntity:insertIntoManagedObjectContext:
在第一次在 moc 中实例化对象时被调用,无论它们是新创建的还是通过请求获取的。因此,该文档似乎具有误导性:
如果 context 不是 nil,这个方法调用 [context insertObject:self] (这会导致调用 awakeFromInsert)。
如果调用它进行获取,则调用awakeFromFetch
。
正如您在回溯中看到的,崩溃发生在我被覆盖的initWithEntity:insertIntoManagedObjectContext:
中。足够合理,initWithEntity:insertIntoManagedObjectContext:
原来是导致崩溃的原因。但我不认为我在这件事上做错了什么。我只是用了一些持久化的属性来初始化一些非持久化的属性:
- (id)initWithEntity:(NSEntityDescription *)entity insertIntoManagedObjectContext:(NSManagedObjectContext *)context
self = [super initWithEntity:entity insertIntoManagedObjectContext:context];
if (self)
// xxx_ is a persistent attribute while _xxx is the corresponding non-persistent attribute as its cache in memory only to reduce reading cost.
_xxx = self.xxx_.unsignedIntegerValue;
return self;
有时当我读取第一个持久属性时,会抛出无法实现的异常。
我一直都知道
不鼓励你重写这个方法——你应该改为 覆盖 awakeFromInsert 和/或 awakeFromFetch (如果有逻辑 这些方法共有的,它应该被考虑到第三种方法中 从两者调用)。如果您执行自定义初始化 在这种方法中,您可能会导致撤消和重做操作出现问题。
但我不知道这会导致无法实现的异常。它是核心数据错误吗?否则,我的代码有什么问题?
【讨论】:
以上是关于这次核心数据崩溃的可能原因是啥的主要内容,如果未能解决你的问题,请参考以下文章