NSFetchedResultsController 是为了在 iOS 和 macOS 中与 UITableViewUICollectionView 配合使用而设计的。它优化了 CoreData 数据的提取和呈现,特别是在滚动列表时。然而,当数据发生更改并使用 fetchLimit 时,可能会遇到显示的数据数量与期望不符的问题。

为何 fetchLimit 似乎未生效?

当设置了 fetchLimit 并进行数据更改后,NSFetchedResultsController 会尝试保持与数据源的一致性。如果数据源中有新对象添加或已有对象被修改,NSFetchedResultsController 会更新结果集来反映这些更改。这可能导致显示的对象数量超过预设的 fetchLimit

解决方法

1. 重新创建 NSFetchedResultsController

在数据发生更改时,可以考虑重新创建一个新的 NSFetchedResultsController 实例,并再次设置好 fetchLimit,然后执行新的 fetch 请求。此方法可能会带来额外的性能开销。

let fetchRequest: NSFetchRequest<EntityName> = EntityName.fetchRequest()
fetchRequest.fetchLimit = 10
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)

2. 使用 Predicate

除了设置 fetchLimit,还可以使用 NSPredicate 来限制返回的对象数量。例如,假设每个对象有一个递增的 id,可以设置一个 predicate 以仅获取 id 小于某个特定值的对象。

let predicate = NSPredicate(format: "id < %@", NSNumber(value: someValue))
fetchRequest.predicate = predicate

这种方式可以与 fetchLimit 结合使用,或作为其独立的限制方法。

3. 手动控制更改

NSFetchedResultsController 的代理方法通知数据发生了更改时,可以手动筛选这些更改,仅保留真正希望在界面上显示的更改。这通常涉及在代理回调中进行一些逻辑判断,确定是否要应用特定的更改到界面上。

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    switch type {
    case .insert:
        // 检查是否超过了 fetchLimit,如果是,则可能不执行插入操作
        break
    // ... 处理其他类型的更改
    default:
        break
    }
}

结论

当使用 NSFetchedResultsController 与动态更改的数据一起工作时,可能需要对其默认的行为进行一些调整以确保界面上的数据与预期相符。上述方法提供了一些可能的策略,开发者可以根据应用的具体需求和特点选择合适的解决方案。