fetchBatchSize 和 NSFetchedResultsController 中的缓存被忽略

fetchBatchSize and cache in NSFetchedResultsController ignored

我正在使用 NSFetchedResultsController 在聊天室应用程序中显示消息。

上下文变量在 appDelegate 中分配,并在聊天室中引用该上下文。

let context = persistentContainer.viewContext

我在viewDidLoad中初始化NSFRC如下:

func initializeResultsController() {
    let request = NSFetchRequest<Message>(entityName: "Message")
    let messageSort = NSSortDescriptor(key: "dateCreated", ascending: true)

    request.sortDescriptors = [messageSort]
    request.predicate = NSPredicate(format: "chatRoomId == %@", self.chatRoomId)
    request.fetchBatchSize = 30

    fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "messageDateSectionIdentifier", cacheName: self.chatRoomId)
    fetchedResultsController.delegate = self

    do {
        try fetchedResultsController.performFetch()
    } catch {
        fatalError("Failed to initialize FetchedResultsController: \(error)")
    }
}

sectionNameKeyPath ("messageDateSectionIdentifier") 是派生的属性,因此可以将部分划分为日历日。

我有两个问题。首先 batchSize 似乎被忽略了,其次缓存似乎对性能没有影响。消息越多,选择聊天室时的延迟时间越长。 1500 条消息大约需要 1 秒。

当我编辑方案以在控制台中显示 coreData 信息时,当视图首次出现时,对 30 行的批处理请求执行了多次,在一种情况下,数组大小为 1500。不确定这是错误数组还是填充的数组。控制台打印输出为:

CoreData: annotation: sql connection fetch time: 0.0013s
CoreData: annotation: total fetch execution time: 0.0014s for 1454 rows.
CoreData: annotation: Bound intarray _Z_intarray0
CoreData: annotation: Bound intarray values.

此后重复多次,值为 30 行。

我尝试将 sectionNameKeyPath 简化为 dateCreated 以查看派生部分是否是问题所在,但根本没有区别。我还应该提到,与所有聊天应用程序一样,该应用程序在显示时最初会滚动到底部。

我想要的是让缓存正常工作,同时让 fetchBatchSize 正常工作,以便最初只从 coreData 获取 30 行,直到用户开始向上滚动。这种方法造成的延迟现在对我的应用程序性能产生了可衡量的影响。

batchSize 不被 fetchedResultsController 尊重是正确的。 NSFetchedResultsController 执行一次获取,然后跟踪上下文中的所有更改,以查看是否添加、删除、移动或更改了任何内容。如果它仅通过尊重 batchSize 获取匹配实体的一个子集,它将无法完成它的工作。

您可以通过将谓词设置为仅在特定日期后获取消息来解决此问题。为了弄清楚截止日期是什么,您可以先进行一次提取,其中 batchSize = 1 和 batchOffset = [您最初想要在 fetchedResultsController 中接收多少消息]。随着越来越多的消息进入集合,其大小将超过您的初始限制。

另请注意,会为集合中的每个元素调用 sectionNameKeyPath。因此,即使在那里做少量工作也会造成巨大的延误。不要在 sectionNameKeyPath 中创建日历或 dataFormatter - 重复使用一个。

我终于找到问题的原因了。

如果您在 tableView heightForRowAt 中引用 fetchedResultsController,则 fetchBatchSize 将循环并加载您指定的 fetchBatchSize 循环中的所有数据。

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    let item = self.fetchedResultsController!.object(at: indexPath)

    // get and return height of item
    return item.heightOfItem
}

如果您使用 UITableViewAutomaticDimension 或定义不需要引用 fetchedResultsController 的高度(即固定高度),那么您将不会遇到此问题,并且 fetchBatchSize 将正常工作。

不幸的是,我发现 UITableViewAutomaticDimension 的滚动性能不可接受,所以我想我必须使用偏移手动配置批量加载。

我还有一个导致循环加载数据的问题。那是 sectionNameKeyPath 在我的例子中是一个短暂的 属性。这也造成了问题,但不幸的是这是必要的。

如果您在使用 NSFetchedResultsController 的 fetchBatchSize 时遇到问题,我建议您查看这两个问题。