优化由NSFetcchedResultsController支持的UITableView

时间:2014-02-21 14:48:48

标签: ios objective-c uitableview core-data nsfetchedresultscontroller

我最近将CoreData支持的UITableView切换为使用NSFetchedResultsController而不是NSArray。对于其中一个表格,滚动现在非常慢,我想我知道为什么,但我不知道解决这个问题的最佳解决方案是什么。

有两个实体BookAuthor处于多对多关系中。每个单元格显示书名和作者。如果有多个作者,则只显示主作者。每个Author都有一个“order”属性,该属性在导入数据时设置。

到目前为止我一直在做的是每次访问作者姓名时,我的Book类都会返回一个mainAuthor属性(NSString):

- (NSString *) mainAuthor
{
    if (!mainAuthor)
    {
       NSSortDescriptor *sortOrder= [NSSortDescriptor sortDescriptorWithKey: @"order" ascending: YES];
       NSArray *authorsSorted = [self.authors sortedArrayUsingDescriptors: @[sortOrder]];

       Author *a = authorsSorted[0];
       mainAuthor = [NSString stringWithFormat: @"%@", a.name];
    }

    return mainAuthor;
}

无论出于何种原因,现在多次调用它而不是仅调用一次并导致速度减慢。滚动表格时,NSFetchedResultsController可能会反复提取引用吗?

那我怎么解决这个问题呢?一种可能性是使mainAuthor成为属性而不是属性。因此,在导入数据时立即设置它。但是在我开始搞乱我的dataModel之前,我想知道这是否是前进的方法,或者是否有另一种解决方案?

更新1:这是我设置fetchController的代码:

- (NSFetchedResultsController *)fetchedResultsController
{    
if (_fetchedResultsController != nil) {
    return _fetchedResultsController;
}

NSManagedObjectContext *moc = [NSManagedObjectContext MR_defaultContext];

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName: @"Book"];

// sort the books by publishing date
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey: @"date" ascending: YES];
[fetchRequest setSortDescriptors: @[sort]];

// only get the books that belong to the library of the current viewController
NSPredicate *predicate = [NSPredicate predicateWithFormat: @"libraries contains[cd] %@", self.library];
[fetchRequest setPredicate: predicate];

[fetchRequest setRelationshipKeyPathsForPrefetching: @[@"authors"]];
[fetchRequest setFetchBatchSize: 10];

NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest: fetchRequest
                                                                      managedObjectContext: moc
                                                                        sectionNameKeyPath: nil
                                                                                 cacheName: nil];

frc.delegate = self;

_fetchedResultsController = frc;

return _fetchedResultsController;
}

1 个答案:

答案 0 :(得分:1)

根据您的示例代码,我假设您的核心数据架构中没有mainAuthor。当Core Data处理托管对象的生命周期(故障)时,您应该将此属性添加到Book实体,以通过使用transient属性来避免不可预测的结果。

尽管如此,我建议您返回一个Author对象而不是NSString,因为您可能会在以后更改该属性的名称,或者想要使用mainAuthor的其他信息在你的用户界面。

瞬态属性

向您的mainAuthor实体添加一个临时属性Book,并为您的Book类添加自定义访问者:

- (Author *)mainAuthor
{
    [self willAccessValueForKey:@"mainAuthor"];
    Author *value = [self primitiveValueForKey:@"mainAuthor"];
    [self didAccessValueForKey:@"mainAuthor"];

    if (value == nil)
    {
        NSPredicate *filterByMinOrder = [NSPredicate predicateWithFormat:@"order == %@.@min.order", self.authors];
        value = [[self.authors filteredSetUsingPredicate:filterByMinOrder] anyObject];
        [self setPrimitiveValue:value forKey:@"mainAuthor"];
    }

    return value;
}

使用瞬态的缺点是必须确保数据在相应book的生命周期内始终是最新的。因此,您必须重置mainAuthor

  • willTurnIntoFault
  • awakeFromFetch
  • awakeFromSnapshotEvents:

(可选,但如果用户可以更改数据则必须)

  • addAuthorObject:
  • removeAuthorObject:

致电[self setPrimitiveValue:nil forKey:@"mainAuthor"]

提示:创建合成primitiveMainAuthor而不是使用primitiveValue:forKey:越来越好:Managed Object Accessor Methods


更新

您是否尝试在fetchBatchSize的{​​{1}}中设置NSFetchedResultsControllerDocs: NSFetchRequest fetchBatchSize


更新2

是的,在这种情况下,必须在fetchRequest中设置适当的关系。

要识别瓶颈,设置调试参数setRelationshipKeyPathsForPrefetching以查看Core Data创建的SQL语句也非常有用。这通常也有助于理解不同的-com.apple.CoreData.SQLDebug 1属性及其影响。