我有一个包含三个实体的核心数据模型:Person
,Group
和Photo
之间的关系如下:
当我使用
NSFetchedResultsController
中的UITableView
执行提取时,我想使用Person
的实体Group
属性将name
对象分组。为此,我使用
sectionNameKeyPath:@"group.name"
。问题是,当我使用
Group
关系中的属性时,NSFetchedResultsController
以20个小批量(我有setFetchBatchSize: 20
)的方式预先提取所有内容,而不是在滚动tableView
时获取批次。如果我使用
Person
实体的属性(例如sectionNameKeyPath:@"name"
)来创建节,则一切正常,一切正常:滚动时NSFetchResultsController
会加载20个对象的小批量。我用来实例化NSFetchedResultsController的代码:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:[Person description]
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Specify how the fetched objects should be sorted
NSSortDescriptor *groupSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"group.name"
ascending:YES];
NSSortDescriptor *personSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"birthName"
ascending:YES
selector:@selector(localizedStandardCompare:)];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:groupSortDescriptor, personSortDescriptor, nil]];
[fetchRequest setRelationshipKeyPathsForPrefetching:@[@"group", @"photo"]];
[fetchRequest setFetchBatchSize:20];
NSError *error = nil;
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(@"Error Fetching: %@", error);
}
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"group.name" cacheName:@"masterCache"];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
如果我基于
"group.name"
创建部分而不与应用程序的UI进行任何交互,这就是我在Instruments中得到的:如果sectionNameKeyPath为nil,这就是我得到的(在UITableView上稍作滚动):
请,有人可以帮我解决这个问题吗?
编辑1:
看来我从模拟器和仪器获得的结果不一致:当我问了这个问题时,使用上述代码,该应用程序在大约10秒钟内(由Time Profiler)在模拟器中启动。
但是今天,使用与上面相同的代码,该应用程序将在900毫秒内在模拟器中启动,即使该应用程序临时对所有对象进行了预取也没有阻塞UI。
我已经附上了一些新的截图:
编辑2:
我重置了模拟器,结果很吸引人:执行导入操作并退出应用程序后,第一次运行如下所示:
滚动一下之后:
现在,这是第二次运行时发生的情况:
第五次运行后:
编辑3:
第七次和第八次运行该应用程序,我得到以下信息:
最佳答案
这是您声明的目标:“我需要按关系实体Group,name属性和NSFetchResultsController将Person对象按节分组,以在我滚动时小批量执行获取操作,而不是像现在这样预先进行。”
答案有点复杂,主要是因为NSFetchedResultsController
如何构建节以及如何影响获取行为。
TL; DR;要更改此行为,您需要更改NSFetchedResultsController构建节的方式。
怎么了?
当给NSFetchedResultsController
一个带有分页的获取请求(fetchLimit和/或fetchBatchSize)时,会发生几件事。
如果未指定sectionNameKeyPath
,则其功能完全符合您的期望。提取返回结果的代理数组,其中第一个fetchBathSize项具有“真实”对象。因此,例如,如果您将setFetchBatchSize
设置为2,并且您的谓词与商店中的10个项目匹配,则结果包含前两个对象。其他对象在访问时将分别获取。这提供了平滑的分页响应体验。
但是,当指定了sectionNameKeyPath
时,提取结果 Controller 必须执行更多操作。要计算这些部分,需要访问结果中所有对象上的关键路径。在我们的示例中,它枚举了结果中的10个项目。前两个已经获取。其他8将在枚举期间获取,以获取构建段信息所需的关键路径值。如果获取请求的结果很多,那么效率可能非常低。关于此功能,存在许多公共(public)错误:
NSFetchedResultsController initially takes too long to set up sections
NSFetchedResultsController ignores fetchLimit property
NSFetchedResultsController, Table Index, and Batched Fetch Performance Issue
...还有其他几个。当您考虑它时,这是有道理的。要构建NSFetchedResultsSectionInfo
对象,需要获取的结果 Controller 查看sectionNameKeyPath
结果中的每个值,将它们聚合为唯一的值联合,然后使用该信息来创建正确数量的NSFetchedResultsSectionInfo
对象,设置名称和索引标题,知道一个节包含的结果中有多少个对象,等等。要处理一般用例,就无法解决这个问题。考虑到这一点,您的Instruments跟踪可能更有意义。
您该如何更改?
您可以尝试构建自己的NSFetchedResultsController
,它为构建NSFetchedResultsSectionInf
o对象提供了另一种策略,但是您可能会遇到一些相同的问题。例如,如果您使用现有的fetchedObjects功能访问访存结果的成员,则在访问属于故障的对象时会遇到相同的行为。您的实现将需要一种策略来解决此问题(这是可行的,但非常取决于您的需求和要求)。
哦,天哪。那么某种临时性的hack会使其性能好一些却不能解决问题呢?
更改数据模型不会改变上述行为,但是会稍微影响性能。批处理更新不会对此行为产生任何重大影响,并且实际上在获取的结果 Controller 上不能很好地发挥作用。但是,将relationshipKeyPathsForPrefetching
设置为包括您的“组”关系可能对您更有用,这可以显着改善获取和错误处理的行为。另一种策略可能是执行另一次提取以对这些对象进行批处理故障,然后再尝试使用获取的结果 Controller ,这将以更有效的方式填充各种级别的Core Data内存中缓存。NSFetchedResultsController
缓存主要用于段信息。这样可以避免在每次更改时都必须完全重新计算这些部分(在最佳情况下),但是实际上可以使构建这些部分的初始获取花费更长的时间。您将不得不进行实验,以了解高速缓存对于您的用例是否值得。
如果您主要关心的是这些Core Data操作正在阻止用户交互,则可以从主线程中卸载它们。 NSFetchedResultsController can be used on a private queue (background) context,这将防止核心数据操作阻止UI。
关于ios - 核心数据sectionNameKeyPath与关系属性性能问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25531438/