为了避免埋葬,我将打开一个核心问题:为什么我的NSFetchedResultsController的fetchedObjects数组通常是同质的,但在极少数情况下它应该包含的托管对象中包含__NSCFString
?
我有一个已经投入生产很长时间的应用程序。它的主 View 是一个表格 View ,其中包含视频列表,并由核心数据托管对象支持。表格 View Controller 使用配置了相当普通的NSFetchedResultsController
的NSFetchRequest
:
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:[ABCVideo entityName]];
NSString *sectionKeyPath = nil;
request.fetchBatchSize = 20;
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:ABCVideoAttributes.recordingDate ascending:NO];
sectionKeyPath = @"sectionIdentifier";
request.sortDescriptors = @[sort];
request.predicate = [NSPredicate predicateWithFormat:@"owner = %@ and %K = %@", person, ABCVideoAttributes.serverDeleted, @(NO)];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:moc sectionNameKeyPath:sectionKeyPath cacheName:kABCMyVideosTableViewControllerCacheKey];
由于这些视频可以上传到云中,因此此表 View Controller 偶尔会收到通知,以更新表 View 单元格中与当前正在上传的视频相对应的进度条。在此回调中,我们获取
NSFetchedResultsController
的fetchedObjects
数组以查找与通知相对应的视频,以便正确的表格 View 单元格可以更新其进度栏。这一切正常。 99.9%的时间,每次
</RonBurgundy>
都可以工作。但是我在HockeyApp崩溃报告中注意到,在一种罕见的情况下,当我的通知处理程序试图从
filteredArrayUsingPredicate
获取fetchedObjects
时,我遇到了SIGABRT:*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0x136f24480> valueForUndefinedKey:]: this class is not key value coding-compliant for the key guid.'
最近,我设法找到一种情况,偶尔可以重现此崩溃,经过大量实验,我发现
fetchedObjects
数组有时包含的不是ABCVideo
的东西:相反,该数组中的一个插槽被__NSCFString
占用了实例。鉴于NSFetchRequestResultType
是NSManagedObjectResultType
,并且字符串不是托管对象,这真是令人惊讶。所以我想知道:这是Core Data错误吗?还是我的数组包含一个以前指向已释放的
ABCVideo
实例的指针,堆中的该位置随后被__NSCFString
实例占用了?如果是后者,那怎么会发生?我正在使用ARC,因此很难理解如何将其中一个视频释放。 最佳答案
-[NSFetchedResultsController fetchedObjects]
和NSFastEnumeration
中存在内存管理错误。对象0x136f24480是ABCVideo,但已释放。该内存用于存储__NSCFString。将消息发送到错误的对象EXC_BAD_ACCESS和SIGABRT是常见的结果。
此错误也存在于我的应用程序中,但是即使一次也无法重现。如果您愿意分享一个可以重现该问题的示例项目,我们可以共同解决。
有多种解决方法。关键是要避免NSFastEnumeration
上的fetchedObjects
// 1
NSArray *fetchedObjects = controller.fetchedObjects
for (int i = 0; i < fetchedObjects.count; ++i) {
NSManagedObject *object = fetchedObjects[i];
}
// 2
NSArray <id<NSFetchedResultsSectionInfo>> *sections = controller.sections;
for (int s = 0; s < sections.count; ++s) {
id<NSFetchedResultsSectionInfo> section = sections[s];
for (int i = 0; i < [section numberOfObjects]; ++i) {
NSManagedObject *object = [controller objectAtIndexPath:[NSIndexPath indexPathForItem:i inSection:s]];
}
}
// 3 Fetch from NSManagedContextDirectly
如果不幸的是,有人读这篇文章使用的是Swift,那么即使调用
fetchedObjects
也会导致崩溃,因为Swift使用NSArray
将Array
转换为NSFastEnumeration
。