ios - 核心数据sectionNameKeyPath与关系属性性能问题

标签 ios objective-c uitableview core-data nsfetchedresultscontroller

我有一个包含三个实体的核心数据模型:PersonGroupPhoto之间的关系如下:

  • 人<< ----------->组(一对多关系)
  • 人<------------->照片(一对一)

  • 当我使用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/

    相关文章:

    objective-c - NSThread 类的对象在没有池的情况下自动释放

    ios - 如何将数据从 viewController 分配给 tableViewCell 标签?

    iphone - 登录钥匙串(keychain)中的 APNs 证书看起来不同

    ios - 自动从 Firebase 中删除过期的条目

    iphone - 这是对 Objective-C 指针的有效使用吗?

    ios - 需要将值从我的 tableView 传递到另一个 View 上的标签?

    ios - 仅在 didSelectRowAtIndexPath 中长按

    android - 从移动浏览器的初始视口(viewport)中排除 DIV

    ios - 没有用于分配给属性的 setter 方法

    iphone - 比较两个数组的问题