看了一段時(shí)間的CoreData Codes,隨著涉獵越多,便越發(fā)覺(jué)得CoreData設(shè)計(jì)之精妙。今日在此分享CoreData之NSFetchedResultsController的妙用。
簡(jiǎn)介
NSFetchedResultsController的設(shè)計(jì)初衷是為了在UITableview中高效地管理那些從Core Data中fetch得到的數(shù)據(jù)。UITableView使用fetch request得到的數(shù)據(jù)來(lái)配置table cell,當(dāng)Core Data中的數(shù)據(jù)產(chǎn)生變化時(shí),NSFetchedResultsController能夠有效地對(duì)得到的結(jié)果進(jìn)行分析,并對(duì)UITableView進(jìn)行調(diào)整。
當(dāng)Core Data中數(shù)據(jù)產(chǎn)生變化,使得UITableView中展示的內(nèi)容需要更新時(shí),NSFetchedResultsControllerDelegate能夠幫助開(kāi)發(fā)者大大減少代碼量,節(jié)省不少時(shí)間。
TapList-Start
下面,就一個(gè)簡(jiǎn)單的例子CoreData-TapList,來(lái)看下NSFetchedResultsController的強(qiáng)大力量。
下載并打開(kāi)工程,首先來(lái)看TapList的Data Model:

該Model中只有一個(gè)Entity,名為Item。每個(gè)item有3個(gè)屬性,其中name代表名字,score是代表分?jǐn)?shù),image代表這個(gè)item的圖片。
table view中的一個(gè)cell表示一個(gè)item,cell布局如下:

table view把Core Data中存儲(chǔ)的所有item按score從高到低的順序排列。當(dāng)點(diǎn)擊某個(gè)cell的時(shí)候,該cell對(duì)應(yīng)的item的score就加1,table view要根據(jù)最新的數(shù)據(jù)對(duì)cell進(jìn)行實(shí)時(shí)更新。
以上就是TapList這個(gè)小應(yīng)用的主體了。如果不用NSFetchedResultsController,該應(yīng)用將在Core Data中數(shù)據(jù)有變化的時(shí)候調(diào)用reloadTable方法來(lái)實(shí)時(shí)更新顯示界面。接下來(lái),我將在此基礎(chǔ)上介紹引入NSFetchedResultsController的實(shí)現(xiàn)方式,你可以看到NSFetchedResultsController帶來(lái)的便利。
TapList-NSFetchedResultsController
在原代碼中將updateTableView方法和tableData全部刪去。雖然這會(huì)導(dǎo)致Xcode報(bào)出不少bug出來(lái),但沒(méi)關(guān)系,慢慢來(lái)。
1.在ViewController中添加一個(gè)NSFetchedResultsController屬性:
var fetchedResultsController: NSFetchedResultsController!
2.在viewDidLoad中,修改代碼如下:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = CGFloat(76)
self.tableView.rowHeight = CGFloat(76)
let fetchRequest = NSFetchRequest(entityName: "Item")
let sortDescriptor = NSSortDescriptor(key: "score", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.context, sectionNameKeyPath: nil, cacheName: nil)
do {
try fetchedResultsController.performFetch()
} catch let error as NSError {
print("Error: \(error.userInfo)")
}
}
首先構(gòu)建一個(gè)fetchRequest,這時(shí)必須指定fetchRequest的sortDescriptors,因?yàn)閠able view必須按某個(gè)順序來(lái)排列一個(gè)個(gè)cell。
接下來(lái),NSFetchedResultsController使用上一步構(gòu)造的fetchRequest來(lái)初始化自己。
緊接著,fetchedResultsController調(diào)用performFetch方法來(lái)從Core Data中取出數(shù)據(jù)。調(diào)用這個(gè)方法后,不需要reloadTable了,因?yàn)閒etchedResultsController會(huì)根據(jù)你所定義的UITableViewDataSource相關(guān)方法,來(lái)替你配置cell并更新UI。
3.在UITableViewDataSource相關(guān)方法中,實(shí)現(xiàn)如下:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedResultsController.fetchedObjects!.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ItemCell", forIndexPath: indexPath) as! ItemCell
cell.item = fetchedResultsController.objectAtIndexPath(indexPath) as? Item
return cell
}
其中,fetchedResultsController.fetchedObjects記錄了所有fetch得到的item,能根據(jù)其數(shù)目確定cell的數(shù)量,并配置相應(yīng)cell。
4.在didSelectRowAtIndexPath中更新如下:
let item = fetchedResultsController.objectAtIndexPath(indexPath) as! Item
5.extension ViewController,使其conform NSFetchedResultsControllerDelegate 協(xié)議:
extension ViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Automatic)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)
case .Update:
let cell = tableView.cellForRowAtIndexPath(indexPath!) as! ItemCell
cell.item = fetchedResultsController.objectAtIndexPath(indexPath!) as? Item
case .Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Automatic)
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
}
這部分代碼是使NSFetchedResultsController發(fā)揮作用的關(guān)鍵。主要過(guò)程分為以下3步:
1.在controller的content即將改變的時(shí)候,允許tableView進(jìn)行更新。
2.對(duì)object的各種改變進(jìn)行相應(yīng)處理。其中anObject是發(fā)生變化的物體,indexPath指向改變前,newIndexPath指向改變后。
3.在controller的content改變即將完成時(shí),使tableView結(jié)束更新。
6.最后,別忘了在viewDidLoad設(shè)置fetchedResultsController的delegate:
fetchedResultsController.delegate = self
7.重新運(yùn)行該app,會(huì)發(fā)現(xiàn)該app不僅能正常運(yùn)行,記錄并實(shí)時(shí)更新item的信息,而且多了一些動(dòng)畫(huà)效果。這也是NSFetchedResultsController帶來(lái)的Bonus之一。
結(jié)語(yǔ)
最終Demo已經(jīng)上傳到這里,希望這篇文章對(duì)你有所幫助_。