iOS CoreData:NSFetchedResultsController性能

时间:2010-09-26 02:58:54

标签: iphone performance core-data nsfetchedresultscontroller

在模型中,我有两个实体:记录和类别。类别是一对多,记录通过反向关系。持久存储是SQLITE类型,db不是那么小,大约23MB(17k记录)。

我使用list-detail设计来显示记录表和详细记录视图。list viewController使用NSFetchedResultsController。

在设备上构建,如果我不使用setFetchBatchSize:

CoreData:annotation:sql连接获取时间:15.8800s CoreData:注释:总获取执行时间:17028行为16.9198。

OMG!

如果我使用setFetchBatchSize:25,那么一切都会再次发挥作用:

CoreData:annotation:sql连接获取时间:1.1736s CoreData:注释:总获取执行时间:1709行为1.1900。

是的,那太好了!但事实并非如此!在列表viewController中,当用户点击一条记录时,我会分配一个详细的viewController,并在fetchedResultsController中的indexPath处传递记录:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Record *record = (Record *)[fetchedResultsController objectAtIndexPath:indexPath];
RecordViewController *recordViewController= [[RecordViewController alloc] init];
    recordViewController.record = record;
    [self.navigationController pushViewController:recordViewController animated:YES];
    [recordViewController release];
}

现在,在详细的viewController中,我有一个按钮可以将记录设置为收藏夹:

- (IBAction) setFavorite {
if (![record.ISFAV intValue]) 
[record setValue:[NSNumber numberWithInt:1] forKey:@"ISFAV"];

else 
[record setValue:[NSNumber numberWithInt:0] forKey:@"ISFAV"];

###SAVE ON THE CONTEXT HERE###

}
好的,你准备好了吗?如果我点击列表中的第一条记录,然后我从收藏夹中添加或删除它,它立即发生在0.0046秒!具有SQL调试模式的控制台仅显示UPDATE语句:

CoreData:sql:BEGIN EXCLUSIVE CoreData:sql:UPDATE ZRECORD SET ZISFAV = ?, Z_OPT =?在哪里Z_PK =?和Z_OPT =? CoreData:sql:COMMIT CoreData:注释:sql执行时间:0.0046s

如果我快速滚动大列表(我很明显在控制台上找到了批量请求),当我点击多个批量请求到达的记录时,我添加\从收藏夹中删除它,许多很多(太多) !滚动越多它们就越多!)SELECT语句出现在UPDATE之前的控制台中。这意味着总执行时间不可接受(uibutton在iphone上长时间冻结)。

发生了什么事?问题显然与批量获取请求有关。更多提取请求= UPDATE语句之前的更多SELECT语句。这是其中之一:

CoreData:sql:SELECT 0,t0.Z_PK,t0.Z_OPT,t0.ZCONTENT,t0.ZCONTENT2,t0.ZISUSER,t0.ZISFAV,t0.ZTITLE,t0.ZTITLE2,t0.ZID,t0 .ZAUTHOR,t0.ZCATEGORY from ZRECORD t0 where t0.Z_PK IN(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,? ,?,?,?,?,?,?,?)按t0.ZTITLE LIMIT 26排序

如果删除setFetchBatchSize,则没有问题(但启动需要16秒)。似乎当我更新属性ISFAV时,CoreData需要再次执行到达该记录所需的所有fetchRequests,即使我将该记录作为对象传递给详细viewController。

对于这篇长篇文章感到抱歉,我尽量让自己更清楚。 非常感谢,我让自己疯了......

2 个答案:

答案 0 :(得分:3)

正在发生的事情是获取的结果控制器在更新托管对象时看到更改通知,并且必须弄清楚该对象是什么索引,以便它可以告诉其委托该对象已更新。要做到这一点,它会再次检查所有批次选择,直到找到正确的批次。我不确定为什么它不能只缓存这些信息,但显然不是。您是否尝试将段缓存添加到获取的结果控制器?这可能会加快速度(取决于获取的结果控制器是否在此实例中使用该缓存)。您只需在调用-initWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:时指定缓存名称即可。

答案 1 :(得分:2)

首先,我建议避免向用户显示一张包含17K记录的大表;相反,您应该允许用户搜索记录,然后选择其中一个搜索结果。无论如何,如果您想允许用户直接从大表中选择记录,您需要考虑提取过程。

开始检查您是否已在Core Data模型中正确编入了用于将NSPredicate关联设置为NSFetchedResultsController的属性。想想你的“工作集”记录的大小。这应该尽可能小,通常是数百条记录。

在您的情况下,将setFetchBatchSize设置为25可能不合适,因为您希望允许用户浏览17K记录。从17000:25 = 680开始,您将需要多次提取才能达到最新的25条记录。但是获取涉及到底层数据库的实际I / O,以确保所有内容始终与其他线程完成的其他“可能”插入/删除/更新操作同步。

即使您的应用程序不使用具有Core Data的多个线程,Core Data框架也必须检查以验证是否有更改。现在,由于I / O价格昂贵,您需要权衡。将setFetchBatchSize设置为1000将需要在最坏的情况下17次提取达到最新的1000条记录(提高40倍),即使每次提取可能需要“稍微”一些。

除非其他线程修改数据,否则按建议使用缓存可能会带来一些好处。实际上,缓存命中很快,非常快。但是,高速缓存未命中非常昂贵,需要I / O从数据库中获取相关数据。当多个线程同时在同一个数据库上工作时,缓存未命中的可能性会增加(除非这些线程只读取记录)。

您可能想要尝试的另一种可能的解决方案是使用多个线程。您的主线程仅获取初始数量的记录并将其呈现给用户,而另一个使用不同托管对象上下文的线程在后台异步获取另一批记录(使用适当的偏移量)。然后将这些记录移交给主线程进行可视化。

还有一件事。您不应该使用KVC来更新属性的值;出于性能原因,做一些像

这样的事情要好得多
record.ISFAV =  [NSNumber numberWithInt:1];

[record setISFAV:[NSNumber numberWithInt:1]];

只更新一个属性您可能没有注意到差异,但如果您需要使用多个属性,您可能会开始节省大笔费用。