Core Data by tutorials 筆記(三)

2018-02-24 15:54 更新

原文出處: http://chengway.in/post/ji-zhu/core-data-by-tutorials-bi-ji-san

今天繼續(xù)來學(xué)習(xí)Raywenderlich家《Core Data by Tutorials》的第五章,本章將會聚焦在NSFetchedResultsController


Chapter 5: NSFetchedResultsController

作者在開篇就提到了NSFetchedResultsController雖然是一個controller,但是他并不是一個view controller,因?yàn)樗麤]有view。

按本章的目錄梳理一下

一、Introducing the World Cup app

本章要完成一個World Cup App,作者提供了一個基本的Start Project,快速瀏覽一下,原始數(shù)據(jù)保存在seed.json文件中。

二、It all begins with a fetch request...

NSFetchedResultsController大概是可以看做對“NSFetchRequest獲取結(jié)果”這一過程的一種封裝。話不多說,直接上代碼:

//1
let fetchRequest = NSFetchRequest(entityName: "Team")
//2
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
                                         managedObjectContext: coreDataStack.context, 
                                         sectionNameKeyPath: nil,
                                         cacheName: nil)
//3
var error: NSError? = nil
if (!fetchedResultsController.performFetch(&error)) {
    println("Error: \(error?.localizedDescription)")
}

前面介紹過,NSFetchRequest是可以高度定制化的,包括sort descriptors、predicates等

注意一下NSFetchedResultsController初始化需要的兩個必要參數(shù)fetchRequest和context,第3步由之前的context來performFetch改為NSFetchedResultsController來performFetch,可以看做是NSFetchedResultsController接管了context所做的工作,當(dāng)然NSFetchedResultsController不僅僅是封裝performFetch,他更重要的使命是負(fù)責(zé)協(xié)調(diào)Core Data和Table View顯示之間的同步。這樣一來,你所需要做到工作就只剩下了提供各種定制好的NSFetchRequest給NSFetchedResultsController就好了。

除了封裝了fetch request之外,NSFetchedResultsController內(nèi)部有容器存儲了fetched回來的結(jié)果,可以使用fetchedObjects屬性或objectAtIndexPath方法來獲取到。下面是一些提供的Data source方法:

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return fetchedResultsController.sections!.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let sectionInfo = fetchedResultsController.sections![section] as NSFetchedResultsSectionInfo
    return sectionInfo.numberOfObjects
}

sections 數(shù)組包含的對象實(shí)現(xiàn)了NSFetchedResultsSectionInfo代理,由他來提供title和count信息。接著來看configureCell

func configureCell(cell: TeamCell, indexPath: NSIndexPath) {
    let team =  fetchedResultsController.objectAtIndexPath(indexPath) as Team
    cell.flagImageView.image = UIImage(named: team.imageName)
    cell.teamLabel.text = team.teamName
    cell.scoreLabel.text = "Wins: \(team.wins)"
    }

這里沒有專門的數(shù)組來保存數(shù)據(jù),數(shù)據(jù)都存在fetched results controller中,并通過objectAtIndexPath來獲取。

這里還要注意的一點(diǎn)就是NSFetchedResultsController至少需要設(shè)置一個sort descriptor,標(biāo)準(zhǔn)的fetch request是不需要的,但NSFetchedResultsController涉及到table view的操作,需要知道列表的排列順序。這樣就可以了,按名稱排序:

let sortDescriptor = NSSortDescriptor(key: "teamName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]

三、Grouping results into sections

參加世界杯的有亞、非、歐、大洋洲、南美,中北美及加勒比海等六大洲,球隊(duì)需要按歸屬地(qualifyingZone)分類。qualifyingZone是Team實(shí)體的一個屬性。用NSFetchedResultsController實(shí)現(xiàn)起來相當(dāng)簡單:

fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
                                           managedObjectContext: coreDataStack.context, 
                                           sectionNameKeyPath: "qualifyingZone",
                                           cacheName: nil)

按sectionNameKeyPath分組,使用起來還是相當(dāng)靈活的

sectionNameKeyPath takes a keyPath string. It can take the form of an attribute name such as “qualifyingZone” or “teamName”, or it can drill deep into a Core Data relationship, such as “employee.address.street”.

這里還有一點(diǎn)要特別注意:上面我們將NSSortDescriptor只設(shè)為teamName排序,而當(dāng)使用sectionNameKeyPath為qualifyingZone就會報(bào)錯,正確的方法是在剛才設(shè)置NSSortDescriptor的地方添加key為qualifyingZone的NSSortDescriptor實(shí)例:

    let zoneSort = NSSortDescriptor(key: "qualifyingZone", ascending: true)
    let scoreSort = NSSortDescriptor(key: "wins", ascending: false)
    let nameSort =  NSSortDescriptor(key: "teamName", ascending: true)
    fetchRequest.sortDescriptors = [zoneSort, scoreSort, nameSort]

If you want to separate fetched results using a section keyPath, the first sort descriptor’s attribute must match the key path’s attribute.
如果要按section keyPath分組,必須創(chuàng)建一個key為key path的NSSortDescriptor實(shí)例,并放在第一位

運(yùn)行一下程序,發(fā)現(xiàn)每個分組內(nèi)的球隊(duì)先是按分?jǐn)?shù)排序,然后才會按姓名,這是因?yàn)閿?shù)組內(nèi)對象的先后順序和排序的優(yōu)先級是相關(guān)的。

fetchRequest.sortDescriptors = [zoneSort, scoreSort, nameSort]

四、“Cache” the ball

將球隊(duì)分組的操作開銷有時候并不小,32支球隊(duì)或許不算什么,但上百萬的數(shù)據(jù)呢,或許你會想到丟掉后臺去操作,這樣確實(shí)不會阻塞主線程UI,但分組在后臺還是會花上很長時間,你還是要loading好久才能有結(jié)果,如果每次fetch都這樣,的確是個頭疼的問題。好的解決辦法就是,只付出一次代價,之后每次可以重用這個結(jié)果。NSFetchedResultsController的作者已經(jīng)想到這個問題了,為我們提供了cahing來解決,打開它就好了。

fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
                                                managedObjectContext: coreDataStack.context, 
                                                sectionNameKeyPath: "qualifyingZone", 
                                                cacheName: "worldCup")

要特別記住的就是這里的section cache?與Core Data中的persistent store是完全獨(dú)立的

NSFetchedResultsController’s section cache is very sensitive to changes in its fetch request. As you can imagine, any changes—such as a different entity description or different sort descriptors—would give you a completely different set of fetched objects, invalidating the cache completely. If you make changes like this, you must delete the existing cache using deleteCacheWithName: or use a different cache name.
section cache其實(shí)相當(dāng)易變,要時刻注意

五、Monitoring changes

最后一個特性,十分強(qiáng)大但容易被濫用,也被作者稱為是雙刃劍。首先NSFetchedResultsController可以監(jiān)聽result set中變化,并且通知他的delegate。你只需要使用他的delegate方法來刷新tableView就ok了。

A fetched results controller can only monitor changes made via the managed object context specified in its initializer. If you create a separate NSManagedObjectContext somewhere else in your app and start making changes there, your delegate method won’t run until those changes have been saved and merged with the fetched results controller’s context.
說白了就是只能監(jiān)聽NSFetchedResultsController初始化傳進(jìn)來的context中的changes,其他不相關(guān)的context是監(jiān)聽不到的,除非合并到這個context中,這個在多線程中會用到。

下面是一個通常的用法

func controllerWillChangeContent(controller: NSFetchedResultsController!) {
    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 TeamCell
            configureCell(cell, indexPath: indexPath)
        case .Move: 
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
            tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Automatic)
        default:
            break
    } 
}
func controllerDidChangeContent(controller: NSFetchedResultsController!) {
    tableView.endUpdates()
}

這個代理方法會被反復(fù)調(diào)用,無論data怎么變,tableView始終與persistent store保持一致。

六、Inserting an underdog

最后作者開了個小玩笑,搖動手機(jī)可以走后門加一支球隊(duì)進(jìn)來(比如中國隊(duì)?)。其實(shí)完全是為了展示Monitoring changes的強(qiáng)大~

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號