Table of Contents
导言:核心数据和NSFetchedResults控制器为何重要
在现代iOS开发中,提供流体,响应的用户界面往往取决于您的应用如何高效地处理随时间变化的数据. 无论您正在构建一个社会信息,任务管理器,还是一个目录系统,显示给用户的数据都很少是静态的. Core Data — 苹果成熟对象图和持久性框架 — 与(FRC)对齐,为管理动态,大规模数据集提供了经过战斗测试的解决方案,同时保持UI同步而不进行人工管理.
本条提供了使用核心数据的深入实用指南,其中通知是关键。当对象被插入、更新或删除时,上下文会广播这些变化,这正是保持用户界面一致性的杠杆。
对于官方开发者文档,参见Apple的核心数据框架参考.
NSFetchedResults 控制器: 核心数据与您的UI之间的桥
是一个控制对象,旨在有效管理从核心数据获取请求中返回的结果,特别是在基础数据预期会改变的情况下。它通过它的代表协议来监测相关的管理对象上下文并自动报告变化。
关键特性
- 自动更改跟踪: FRC听上下文通知,将其翻译为结构化的代表调回(,,]).
- 构建-in 节: 通过指定一个],控制器组将结果获取到各节,使得显示节表视图或集合视图变得微不足道.
- 性能优化:[ FRC在引擎盖下使用断层和缓存,它只根据需要获取数据,并且可以选择使用一个持久性缓存,以避免在管理对象上下文保存时出现重排.
详细代表方法
要从控制器中充分受益,您必须执行。最常见的模式是在或[] 内使用这些调回方式来批量更新UI。例如:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChange anObject: Any,
at indexPath: IndexPath?,
for type: NSFetchedResultsChangeType,
newIndexPath: IndexPath?) {
switch type {
case .insert:
tableView.insertRows(at: [newIndexPath!], with: .fade)
case .delete:
tableView.deleteRows(at: [indexPath!], with: .fade)
case .update:
tableView.reloadRows(at: [indexPath!], with: .fade)
case .move:
tableView.moveRow(at: indexPath!, to: newIndexPath!)
@unknown default:
tableView.reloadData()
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
这种模式确保表格能够查看动画与基础数据同步的变化,防止闪烁或不一致。
分步执行
下面是一个完整的、可以生产的例子,使用Swift 5, 瞄准iOS 15+。 我们将假设一个简单的实体, 名为 ], 属性 (String) 和 (Bool), 以及 (Date) 。
1. 设置核心数据堆栈
在您的或专用(常见于SwiftUI应用)中,创建持久性容器:
class PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init() {
container = NSPersistentContainer(name: "YourModelName")
container.loadPersistentStores { storeDescription, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
2. 创建国家管理对象子类
使用 Xcode 的数据模型编辑器生成类文件, 或者手工创建它们。 确保您的实体使用正确的类模块设置 。
3. 配置获取请求和 FRC
在您视图控制器中, 设置获取请求并初始化获取的结果控制器。 最好在 [[FLT: 19] 中执行此任务, 或者在视图模型的输入中执行 。
lazy var fetchedResultsController: NSFetchedResultsController<Task> = {
let fetchRequest: NSFetchRequest<Task> = Task.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "dueDate", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
// Optional: limit results with batch size for large datasets
fetchRequest.fetchBatchSize = 20
let controller = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: PersistenceController.shared.container.viewContext,
sectionNameKeyPath: "completionStatus", // e.g., a transient attribute or a computed property
cacheName: nil
)
controller.delegate = self
try? controller.performFetch()
return controller
}()
4. 与 FRC 一起驱动表格视图
您的[ [FLT: 21] ] 方法变得微不足道:
func numberOfSections(in tableView: UITableView) -> Int {
fetchedResultsController.sections?.count ?? 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = fetchedResultsController.sections![section]
return sectionInfo.numberOfObjects
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath)
let task = fetchedResultsController.object(at: indexPath)
configure(cell, with: task)
return cell
}
注意我们从未用手动调用——代表方法处理每次插入,删除和更新.
先进使用和最佳做法
线索安全
核心数据背景不安全。 对所有与 UI 相关的获取请求和 FRC 请求, 总是使用 [[FLT: 24]] (运行在主队列上) 。 对于背景工作, 创建私有队列上下文, 并合并视图上下文的更改 。 避免在多个队列中使用相同的 FRC 。
缓存
参数可以通过持续区段和对象信息来改进发射性能。但是,如果获取请求或数据模型发生变化,则必须删除缓存()以避免腐败。
处理大型数据集
使用 [[FLT: 27] ] 限制被带入内存的对象数量。 另外, 只有在您需要立即访问每个属性时, 才考虑设置 [[FLT: 28] 。 对于关系密集型数据, 预先使用 [[FLT: 29] ] 就可以防止重复的断层 。
与 SwiftUI 合并
虽然是UIKit ⁇ central,但您仍可以在SwiftUI中使用,方法是用]包装,或使用较新的属性包装器(内部使用类似机制). 对于SwiftUI中复杂的动态数据,[]通常已经足够,但FRC会给予您精细的 QGraint对动画和批量的控制.
常见的陷阱和如何避免它们
- 忘记 ] : [ 控制器在您调用这种方法之前不会执行抓取。 做一次,通常是在初始化之后。
- 不设置代表: 没有代表,更改不会向表视图传播。总是呼叫]。
- 不正确的排序描述符: 如果您的区域名称密钥路径与第一种描述符不匹配, 区域可能会出现异常排列。 请确保 ] 中使用的属性出现在排序描述符中 。
- 使用 与 FRC :[ 调用 ] FRC 动画变化可造成崩溃。让代表方法处理所有 UI 更新 。
- 忽略核心数据上下文保存失败: 如果您保存上下文并发生错误,则可能不会通知FRC。总是处理错误,并考虑在背景上下文中使用。
业绩考量
已经有效率,但这里还有额外的优化:
- 明智地使用上游: 复杂的上游可以慢化初始获取,尽可能使用索引属性.
- Limit 获取属性 : 如果您只需要某些属性,则在获取请求中设置 .
- 批次更新: 当做许多修改时,将其包裹在一个块中以减少代表召回的次数.
- 避免不必要的错误: 如果你知道会在结果集中访问所有对象,则使用来预挑它们,但要小心内存.
关于更深入地潜入核心数据性能,参见Apple的核心数据性能指南[.
真实的世界实例
例1:聊天应用程序
消息应用程序显示最近消息排序的对话列表。 新来的信息应该立即出现。 使用 FRC , 联系人视图只能订阅当前用户对话实体的更改 。 按日期将消息分组到“ 今天 ” 、 “ 昨天 ” 等 。
例2:库存管理
e commerce app 显示产品类别。 当股票水平从背景同步变化时, FRC 自动更新 UI。 通过设置 到 50, 视图仍然响应, 即使有数千项。
例3:按类别排列的待办事宜
经典实例: 归为“ 过期 ” 、 “ 今天 ” 和 “ 即将到来 ” 的任务。 [[FLT: 45]] 可以是一个从 [[FLT: 46] 和当前日期计算出来的瞬变派属性。 请在排序描述符中使用相同的派生值, 以避免错配 。
结论
掌握核心数据和的组合是构建强大,动态iOS应用程序的基石。 通过卸载变化跟踪和UI同步到苹果框架的重载,您可以专注于创建伟大的用户体验,而不是写入锅炉板数据管理代码。
无论您是否在保留一个遗留的 UIKit app 或用 ] 采用 SwiftUI, 原理保持不变 : 了解您的对象图, 精心配置请求, 让获取的结果控制器做它最擅长的工作。 通过这篇文章中概述的做法, 包括缓存、 线程安全和性能调谐, 您将完全有能力处理任何随时间演变的数据集。
进一步探索时,请检查 Ray Wendelich的核心数据教程和关于NSFetchedResults Captain 的NSHipster文章.