这篇笔记主要是介绍 CoreData 和 TableView 一起使用时候的方法。
使用 CoreData 应用中有很多列表展示的场景底层数据需要读取本地数据,我以前的做法并没有遵循官方文档的做法使用 NSFetchedResultsController (opens new window) 这个类去进行数据管理,而是我自己通过 CoreData 的 API 去读写数据,自己管理 UITableView 刷新的,其实是维护了两份数据源,一份内存中的数据,一份本地的数据,当用户插入数据的时候先通过 CoreData 将数据插入本地,然后再插入内存。这样管理非常费劲,而且在后面加入 iCloud 同步的时候会面临什么时候将本地数据加载入内存中这样的问题。看了官方文档之后才知道有更好的做法,于是变更了自己使用 CoreData+UITableview 的方式,使用官方推荐的 API NSFetchedResultsController
来做列表数据管理。
NSFetchedResultsController: A controller that you use to manage the results of a Core Data fetch request and to display data to the user. 即管理 CoreData 获取结果,并将结果展示给用户的过程。
使用一个简单的例子来快速熟悉 NSFetchedResultsController
的使用,假设我们有 Book
类型
extension Book {
@nonobjc public class func fetchRequest() -> NSFetchRequest<Book> {
return NSFetchRequest<Book>(entityName: "Book")
}
@NSManaged public var name: String?
@NSManaged public var id: String?
}
# NSFetchedResultsController 使用
在
viewDidLoad
中初始化NSFetchedResultsController
。override func viewDidLoad() { //1. 初始化 NSFetchRequest //2. 设置获取数据源时候的排序规则 NSSortDescriptor //3. 使用 NSFetchRequest+CoreDataContext 来初始化 NSFetchedResultsController //4. 设置 NSFetchedResultsController 代理为当前视图控制器 let request = NSFetchRequest<Book>.init(entityName: "Book") let nameSort = NSSortDescriptor(key: "name", ascending: true) request.sortDescriptors = [nameSort] self.fetchedResultsController = NSFetchedResultsController<Book>.init(fetchRequest: request, managedObjectContext: coreDataContext, sectionNameKeyPath: nil, cacheName: nil) self.fetchedResultsController.delegate = self do { try self.fetchedResultsController.performFetch() } catch { NSLog("fetch error \(error)") } }
给
NSFetchedResultsController
设置代理后,这个控制器会注册接收对应ManagedObjectContext
改变的通知。当ManagedObjectContext
发生影响改变结果集(FetchedResults)的时候,NSFetchedResultsController
会通知代理结果的变化,进而更新视图。后面步骤会涉及到这部分。ViewController 需要正常实现 TableView 的代理方法和数据源方法。这里主要用到
self.fetchedResultsController
的 sections 属性。func numberOfSections(in tableView: UITableView) -> Int { return self.fetchedResultsController.sections?.count ?? 0 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let sectionInfo:NSFetchedResultsSectionInfo? = self.fetchedResultsController.sections?[section] return sectionInfo?.numberOfObjects ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell = tableView.dequeueReusableCell(withIdentifier: "cell") if cell == nil { cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell") } let book = self.fetchedResultsController.object(at: indexPath) var content = cell!.defaultContentConfiguration() content.text = book.name cell!.contentConfiguration = content return cell! }
sections
属性类型是实现了[NSFetchedResultsSectionInfo](https://developer.apple.com/documentation/coredata/nsfetchedresultssectioninfo)
协议的实例数组类型。视图控制器需要实现
[NSFetchedResultsControllerDelegate](https://developer.apple.com/documentation/coredata/nsfetchedresultscontrollerdelegate)
协议中的方法。这些代理方法的目的就是数据变动的时候通知界面,让开发者有机会对数据变化做出响应。//Notifies the delegate that section and object changes are about to be processed and notifications will be sent. func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { tableView.beginUpdates() } //Notifies the receiver of the addition or removal of a section. func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) { switch type { case .insert: let indexSet = IndexSet.init(integer: sectionIndex) self.tableView.insertSections(indexSet, with: .fade) break case .delete: let indexSet = IndexSet.init(integer: sectionIndex) self.tableView.deleteSections(indexSet, with: .fade) break case .move: fallthrough case .update: break @unknown default: break } } //Notifies the receiver that a fetched object has been changed due to an add, remove, move, or update. func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { switch type { case .insert: self.tableView.insertRows(at: [newIndexPath!], with: .fade) case .delete: self.tableView.deleteRows(at: Array([indexPath!]), with: .fade) case .update: self.tableView.reloadRows(at: Array([indexPath!]), with: .fade) case .move: self.tableView.reloadData() @unknown default: break // } } //Notifies the delegate that all section and object changes have been sent. func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { tableView.endUpdates() }
当要增加条目的时候只需要向 CoreData 中插入数据,视图就会自动更新了。删除数据同理
func insertBook(with id:String,name:String) { let book = Book.init(context: self.coreDataContext) book.id = id book.name = name do { try coreDataContext.save() } catch { NSLog("core data insert error: \(error)") } }
这样我们就不用再维护内存中的数据了,底层数据变动系统都帮我们做好了。同理 UICollectionView
也可以使用类似的方法来进行数据管理。更详细的说明官方文档和用到的这些类的 API 介绍的都比较清楚,这里不再赘述了。
参考地址: