使用Core Data高效显示100,000个项目

时间:2013-02-05 12:08:21

标签: ipad core-data nsfetchrequest

我正在使用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 - 我能做些什么来加快这个速度?或者我刚刚达到了限制?我一定错过了什么。

2 个答案:

答案 0 :(得分:7)

首先:您可以使用NSFetchedResultsController的缓存来加速第一次获取后的显示。这应该很快就会下降到几分之一秒。

第二:您可以尝试仅显示第一个屏幕,然后在后台获取其余屏幕。我通过以下方式执行此操作:

  • 当视图出现时,检查您是否有第一页缓存。
  • 如果没有,我会抓第一页。您可以通过设置获取请求fetchLimit来完成此操作。
    • 如果您正在使用部分,请执行两次快速提取以确定第一部分标题和记录。
  • 使用后台线程中的长提取来填充第二个提取的结果控制器。
    • 您可以创建子上下文并使用performBlock:
    • 使用dispatch_async()
  • 将第二个FRC分配到表格视图并调用reloadData

这在我最近的一个项目中使用> 200K记录。

答案 1 :(得分:0)

我知道@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 +记录,该过程也非常快,因为通常只有一两个记录被更改。

希望这可以帮助那些坚持这个的人!