使用 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 模型中高效访问数据

Perl 程序高效处理目录中的 500,000 个小文件

Core Data 抛出 NSInternalInconsistencyException “100 次尝试后上下文仍然很脏。”

用于对大型数组进行高效插入和删除的数据结构

我需要创建一个十进制到二进制程序,它可以接收多达 100,000,000 的输入并输出整个答案而不显示垃圾