使用 Core Data 高效显示 100,000 个项目
Posted
技术标签:
【中文标题】使用 Core Data 高效显示 100,000 个项目【英文标题】:efficiently display 100,000 items using Core Data 【发布时间】:2013-02-05 12:08:20 【问题描述】:我正在使用 NSFetchResultsController 在 UITableView 中显示 100,000 多条记录。这可行,但速度很慢,尤其是在 iPad 1 上。加载可能需要 7 秒,这对我的用户来说是一种折磨。
我也希望能够使用部分,但这至少会增加 3 秒的延迟时间。
这是我的 NSFetchResultsController:
- (NSFetchedResultsController *)fetchedResultsController
if (self.clientsController != nil)
return self.clientsController;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Client" inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
[request setPredicate:[NSPredicate predicateWithFormat:@"ManufacturerID==%@", self.manufacturerID]];
[request setFetchBatchSize:25];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"UDF1" ascending:YES];
NSSortDescriptor *sort2= [[NSSortDescriptor alloc] initWithKey:@"Name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObjects:sort, sort2,nil]];
NSArray *propertiesToFetch = [[NSArray alloc] initWithObjects:@"Name", @"ManufacturerID",@"CustomerNumber",@"City", @"StateProvince",@"PostalCode",@"UDF1",@"UDF2", nil];
[request setPropertiesToFetch:propertiesToFetch];
self.clientsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil
cacheName:nil];
return self.clientsController;
我有一个在我的 NSPredicate 中使用的 ManufacturerID 索引。这似乎是一个非常基本的 NSFetchRequest - 我能做些什么来加快速度?还是我刚刚达到了限制?我一定是错过了什么。
【问题讨论】:
你有一个墙大小的屏幕? @vonbrand 不确定这是什么意思? 显示 100,000 条数据需要 巨大的 空间。 它是 UITableView 中的滚动列表 那么你想显示一个变化范围的行,这与所要求的非常不同...... 【参考方案1】:首先:您可以使用NSFetchedResultsController
的缓存来加快第一次获取后的显示速度。这应该很快下降到几分之一秒。
第二:您可以尝试仅显示第一个屏幕,然后在后台获取其余部分。我通过以下方式做到这一点:
当视图出现时,检查是否有第一页缓存。 如果没有,我会获取第一页。您可以通过设置获取请求的fetchLimit
来完成此操作。
如果您正在使用节,请执行两次快速提取以确定第一个节的标题和记录。
在后台线程中使用长提取填充第二个提取结果控制器。
您可以创建子上下文并使用performBlock:
或
使用dispatch_async()
。
将第二个 FRC 分配给表格视图并调用 reloadData
。
这在我最近有超过 20 万条记录的项目中运行良好。
【讨论】:
这是我的想法,但还没有尝试实现它,认为他们不会;滚动列表的速度比我在后台加载更多的速度更快。您是否使用了 2 个单独的 FRC?如果你这样做了,那么我假设在第二个 FRC 加载后设置了一些标志,以切换表从哪个 FRC 馈送 - 听起来对吗? 嗨 Mundi,我想知道,您如何使用NSFetchedResultsController
在后台执行提取。作为,我尝试这样做,但没有任何成功 - ***.com/questions/66789397/…【参考方案2】:
我知道@Mundi 提供的答案已被接受,但我已经尝试实施它并遇到了问题。具体来说,第二个 FRC 创建的对象将基于另一个线程的 ManagedObjectContext。由于这些对象不是线程安全的,并且在另一个线程上属于它们自己的 MOC,因此我找到的解决方案是在加载对象时对其进行故障排除。所以在 cellForRowAtIndexPath 我添加了这一行:
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
object = (TapCellar *)[self.managedObjectContext existingObjectWithID:[object objectID] error:nil];
那么你就有了一个适合你所在线程的对象。另外需要注意的是,你对对象所做的更改不会反映在后台 MOC 中,因此你必须协调它们。我所做的是让后台 MOC 成为私有队列 MOC,而前台是它的子 MOC,如下所示:
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
_privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_privateManagedObjectContext setPersistentStoreCoordinator:coordinator];
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setParentContext:_privateManagedObjectContext];
现在,当我在主线程中进行更改时,我可以通过这样做轻松地协调它们:
if ([self.managedObjectContext hasChanges])
[self.managedObjectContext performBlockAndWait:^
NSError *error = nil;
ZAssert([self.managedObjectContext save:&error], @"Error saving MOC: %@\n%@",
[error localizedDescription], [error userInfo]);
];
我等待它的返回,因为此时我要重新加载表数据,但如果您愿意,可以选择不等待。即使对于 30K+ 记录,该过程也非常快,因为通常只更改一两个。
希望这可以帮助那些陷入困境的人!
【讨论】:
不幸的是,这仍然不是线程安全的。如果您从cellForRowAtIndexPath
中调用[self.fetchedResultsController objectAtIndexPath:indexPath]
,则您正在跨越线程边界。无法保证您请求的 indexPath 仍将指向同一个对象,因为在后台线程中,获取的结果控制器正在侦听托管对象上下文更改并因此更新自身。以上是关于使用 Core Data 高效显示 100,000 个项目的主要内容,如果未能解决你的问题,请参考以下文章
在 Core Data 中存储 400,000 条数据用于地铁路线查找是不是异常?
Core Data 抛出 NSInternalInconsistencyException “100 次尝试后上下文仍然很脏。”