原文出處: http://chengway.in/post/ji-zhu/core-data-by-tutorials-bi-ji-er
今天繼續(xù)來(lái)重溫Raywenderlich家的《Core Data by Tutorials》。學(xué)完前三章就掌握了CoreData的基本知識(shí),現(xiàn)在我們來(lái)深入來(lái)研究下Fetching?
這一章主要深入地探究Fetching,學(xué)完這一章,你將會(huì)加滿如下技能點(diǎn):
還是按作者的主要目錄來(lái)梳理一下吧:
明星登場(chǎng),同樣是創(chuàng)建NSFetchRequest的實(shí)例,并對(duì)其進(jìn)行configure且執(zhí)行??偠灾?,繁重的工作都替你干了,任勞任怨的程度和NSManagedObjectContext有一拼。接著展示了四種創(chuàng)建NSFetchRequest實(shí)例的方式:
//1
let fetchRequest1 = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: managedObjectContext!)
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: managedObjectContext!)
//2
let fetchRequest2 = NSFetchRequest(entityName: "Person")
//3
let fetchRequest3 = managedObjectModel.fetchRequestTemplateForName("peopleFR")
//4
let fetchRequest4 = managedObjectModel.fetchRequestFromTemplateWithName("peopleFR", substitutionVariables: ["NAME" : "Ray"])
本章要實(shí)現(xiàn)一個(gè)泡泡?茶的APP,作者提供了一個(gè)Start project,快速瀏覽一下,主界面是一個(gè)TableView,奶茶店等原始數(shù)據(jù)都保存seed.json之中。接下來(lái)要考慮如何將數(shù)據(jù)從seed中讀取到Core Data的對(duì)象中去。
這里可以在data model中設(shè)置一個(gè)Fetch Request,并選擇Fetch哪些對(duì)象,設(shè)置一些條件;緊接著你就可以使用模板中fetchRequest了:
fetchRequest = coreDataStack.model.fetchRequestTemplateForName("FetchRequest")
這里要使用coreData,并注意FetchRequest名稱要和model中的匹配。
作者把NSFetchRequest比喻成了Core Data framework中的瑞士軍刀,看來(lái)還是相當(dāng)強(qiáng)大的。這里有四種fetch request的result type:
①.NSManagedObjectResultType: Returns managed objects (default value).
②.NSCountResultType: Returns the count of the objects that match the fetch request.
③.NSDictionaryResultType: This is a catch-all return type for returning the results of different calculations.
④.NSManagedObjectIDResultType: Returns unique identifiers instead of full- fledged managed objects.
將fetchRequest.resultType設(shè)為NSCountResultType在查詢大量對(duì)象數(shù)量時(shí)可以極大的優(yōu)化性能。
另一種獲取count的方式是context直接調(diào)用countForFetchRequest方法
let count = coreDataStack.context.countForFetchRequest(fetchRequest, error: &error)
Core Data提供了多種函數(shù)支持來(lái)支持計(jì)算,如average, sum, min and max. 下面是一個(gè)求和的例子:
func populateDealsCountLabel() {
//1 指定result類型
let fetchRequest = NSFetchRequest(entityName: "Venue") fetchRequest.resultType = .DictionaryResultType
//2 創(chuàng)建表達(dá)式描述,指定個(gè)名稱,以便將來(lái)在fetch的結(jié)果(字典類型)中找到
let sumExpressionDesc = NSExpressionDescription() sumExpressionDesc.name = "sumDeals"
//3 創(chuàng)建具體的表達(dá)式
sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments:[NSExpression(forKeyPath: "specialCount")])
sumExpressionDesc.expressionResultType = .Integer32AttributeType
//4 設(shè)置fetch request 將要fetch sum
fetchRequest.propertiesToFetch = [sumExpressionDesc]
//5 執(zhí)行,最后通過(guò)之前設(shè)置的表達(dá)式name得到最終結(jié)果
var error: NSError?
let result = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [NSDictionary]?
if let resultArray = result {
let resultDict = resultArray[0]
let numDeals: AnyObject? = resultDict["sumDeals"] numDealsLabel.text = "\(numDeals!) total deals"
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
現(xiàn)在還剩一種.ManagedObjectIDResultType類型,他返回一個(gè)包含NSManagedObjectID對(duì)象的數(shù)組,NSManagedObjectID 可以看成是managed object通用唯一標(biāo)識(shí),除了多線程的某些場(chǎng)景下,否則很少用到它。
關(guān)于性能優(yōu)化CoreData通過(guò)以下幾種方式來(lái)削減income data:
①.fetch batches,可以使用fetchBatchSize, fetchLimit 和 fetchOffset這些屬性來(lái)削減income data。
②.支持**faulting**這樣的*占位符*。
③.使用predicates
最后要注意一下如果想要在運(yùn)行時(shí)改變predicate,就不能再用model來(lái)初始化fetchRequest了
override func viewDidLoad() {
super.viewDidLoad()
// fetchRequest = coreDataStack.model.fetchRequestTemplateForName("FetchRequest")
fetchRequest = NSFetchRequest(entityName: "Venue") fetchAndReload()
}
至于predicates的用法請(qǐng)查看官方文檔
排序主要用到了NSSortDescriptor這個(gè)類,值得注意的是該排序是真正發(fā)生在SQLite層面的,而不是在內(nèi)存中的,因此該排序十分高效。
NSSortDescriptor實(shí)例初始化需要三個(gè)參數(shù):①.你要排序?qū)傩缘膋ey path ②.指明升序 or 降序 ③. 一個(gè)可選的selector
這里要注意一點(diǎn)就是CoreData不支持block-based API定義NSSortDescriptor,以及基于block-based方式定義NSPredicate。
//下面兩個(gè)初始化方法不能用于CoreData中
+ (instancetype)sortDescriptorWithKey:(NSString *)key ascending:(BOOL)ascending comparator:(NSComparator)cmptr
+ (NSPredicate *)predicateWithBlock:(BOOL (^)(id evaluatedObject, NSDictionary *bindings))block
下面創(chuàng)建了一個(gè)按名字排序filter
lazy var nameSortDescriptor: NSSortDescriptor = {
var sd = NSSortDescriptor(key: "name",
ascending: true,
selector: "localizedStandardCompare:")
return sd
}()
這里注意一下第三個(gè)參數(shù)localizedStandardCompare,傳入這個(gè)參數(shù)就會(huì)按本地語(yǔ)言排序,這也是蘋(píng)果官方推薦的一種做法。
得到一個(gè)反轉(zhuǎn)排序的sortDescriptor也很簡(jiǎn)單,這樣就可以了
selectedSortDescriptor = nameSortDescriptor.reversedSortDescriptor
iOS 8蘋(píng)果推出全新的API來(lái)處理異步fetch問(wèn)題,NSAsynchronousFetchRequest,這里不要被他的名字迷惑了,和NSFetchRequest沒(méi)什么直接關(guān)系,他其實(shí)是NSPersistentStoreRequest的子類。下面是該類的定義及初始化方法:
@availability(iOS, introduced=8.0)
class NSAsynchronousFetchRequest : NSPersistentStoreRequest {
var fetchRequest: NSFetchRequest { get }
var completionBlock: NSPersistentStoreAsynchronousFetchResultCompletionBlock? { get }
var estimatedResultCount: Int
init(fetchRequest request: NSFetchRequest, completionBlock blk: NSPersistentStoreAsynchronousFetchResultCompletionBlock?)
}
從該類的初始化方法可以將異步fetchRequest看做是對(duì)fetchRequest的一種包裝,第一個(gè)參數(shù)是標(biāo)準(zhǔn)的NSFetchRequest 對(duì)象,而第二個(gè)參數(shù)是一個(gè)a completion handler,不過(guò)僅僅有completion handler是不夠的,最后還是需要executeRequest,下面是一個(gè)完整的例子:
//1
fetchRequest = NSFetchRequest(entityName: "Venue")
//2
asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) {
[unowned self] (result: NSAsynchronousFetchResult! ) -> Void in
self.venues = result.finalResult as [Venue]
self.tableView.reloadData()
}
//3
var error: NSError?
let results = coreDataStack.context.executeRequest(asyncFetchRequest, error: &error)
if let persistentStoreResults = results {
//Returns immediately, cancel here if you want
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
這段代碼注意executeRequest一旦執(zhí)行立即返回一個(gè)NSAsynchronousFetchResult類型的結(jié)果,在這里你不用操心fetch到的數(shù)據(jù)和UI匹配,這些工作都在第二步handle那里處理了。另外NSAsynchronousFetchResult是有cancel()方法的。
僅僅有新API還不夠,還要修改context
context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
另外一種.PrivateQueueConcurrencyType將在后面多線程介紹
因?yàn)槭钱惒絝etch,所以可能tableview初始化完畢后,fetch的結(jié)果才到,這里給數(shù)據(jù)源可以設(shè)置一個(gè)空數(shù)組來(lái)fix。
有的時(shí)候你需要一次更新很多屬性,全部fetch到內(nèi)存顯然是不高效的,iOS 8推出了全新的批量更新(batch updates)來(lái)解決這一痛點(diǎn)。這個(gè)類就是NSBatchUpdateRequest,和上面提到的NSAsynchronousFetchRequest都是NSPersistentStoreRequest的子類,下面是這個(gè)類的定義:
@availability(iOS, introduced=8.0)
class NSBatchUpdateRequest : NSPersistentStoreRequest {
init(entityName: String)
init(entity: NSEntityDescription)
var entityName: String { get }
var entity: NSEntityDescription { get }
var predicate: NSPredicate?
// Should the update include subentities? Defaults to YES.
var includesSubentities: Bool
// The type of result that should be returned from this request. Defaults to NSStatusOnlyResultType
var resultType: NSBatchUpdateRequestResultType
// Dictionary of NSPropertyDescription|property name string -> constantValue/NSExpression pairs describing the desired updates.
// The expressions can be any NSExpression that evaluates to a scalar value.
var propertiesToUpdate: [NSObject : AnyObject]?
}
具體使用起來(lái)也很簡(jiǎn)單:
let batchUpdate = NSBatchUpdateRequest(entityName: "Venue")
batchUpdate.propertiesToUpdate = ["favorite" : NSNumber(bool: true)]
batchUpdate.affectedStores = coreDataStack.psc.persistentStores
batchUpdate.resultType = .UpdatedObjectsCountResultType
var batchError: NSError?
let batchResult = coreDataStack.context.executeRequest(batchUpdate, error: &batchError) as NSBatchUpdateResult?
if let result = batchResult {
println("Records updated \(result.result!)")
} else {
println("Could not update \(batchError), \(batchError!.userInfo)")
}
最后注意一點(diǎn),就是批量更新跳過(guò)了NSManagedObjectContext,直接對(duì)persistentStore進(jìn)行更新,沒(méi)有經(jīng)過(guò)有效性驗(yàn)證,這個(gè)就要靠你自己確保更新的數(shù)據(jù)合法了。
更多建議: