为了避免埋葬,我将打开一个核心问题:为什么我的NSFetchedResultsController的fetchedObjects数组通常是同质的,但在极少数情况下它应该包含的托管对象中包含__NSCFString

我有一个已经投入生产很长时间的应用程序。它的主 View 是一个表格 View ,其中包含视频列表,并由核心数据托管对象支持。表格 View Controller 使用配置了相当普通的NSFetchedResultsControllerNSFetchRequest:

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 单元格中与当前正在上传的视频相对应的进度条。在此回调中,我们获取NSFetchedResultsControllerfetchedObjects数组以查找与通知相对应的视频,以便正确的表格 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占用了实例。鉴于NSFetchRequestResultTypeNSManagedObjectResultType,并且字符串不是托管对象,这真是令人惊讶。

所以我想知道:这是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使用NSArrayArray转换为NSFastEnumeration

08-17 00:57