🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
> 原文出处: http://chengway.in/post/ji-zhu/core-data-by-tutorials-bi-ji-er 今天继续来重温Raywenderlich家的[《Core Data by Tutorials》](http://www.raywenderlich.com/store/core-data-by-tutorials)。学完前三章就掌握了CoreData的基本知识,现在我们来深入来研究下Fetching? * * * ## **Chapter 4: Intermediate Fetching** 这一章主要深入地探究Fetching,学完这一章,你将会加满如下技能点: * fetch only what you need to; * refine your fetched results using predicates; * fetch in the background to avoid blocking the UI; * avoid unnecessary fetching by updating objects directly in the persistent store. 还是按作者的主要目录来梳理一下吧: ### **一、NSFetchRequest: the star of the show** 明星登场,同样是创建NSFetchRequest的实例,并对其进行configure且执行。总而言之,繁重的工作都替你干了,任劳任怨的程度和NSManagedObjectContext有一拼。接着展示了四种创建NSFetchRequest实例的方式: ~~~ //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"]) ~~~ ### **二、Introducing the Bubble Tea app** 本章要实现一个泡泡?茶的APP,作者提供了一个Start project,快速浏览一下,主界面是一个TableView,奶茶店等原始数据都保存seed.json之中。接下来要考虑如何将数据从seed中读取到Core Data的对象中去。 ### **三、Stored fetch requests** 这里可以在data model中设置一个Fetch Request,并选择Fetch哪些对象,设置一些条件;紧接着你就可以使用模板中fetchRequest了: ~~~ fetchRequest = coreDataStack.model.fetchRequestTemplateForName("FetchRequest") ~~~ 这里要使用coreData,并注意FetchRequest名称要和model中的匹配。 ### **四、Fetching different result types** 作者把NSFetchRequest比喻成了Core Data framework中的瑞士军刀,看来还是相当强大的。这里有四种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**设为**NSCountResultType**在查询大量对象数量时可以极大的优化性能。 另一种获取count的方式是context直接调用**countForFetchRequest**方法 ~~~ let count = coreDataStack.context.countForFetchRequest(fetchRequest, error: &error) ~~~ Core Data提供了多种函数支持来支持计算,如average, sum, min and max. 下面是一个求和的例子: ~~~ func populateDealsCountLabel() { //1 指定result类型 let fetchRequest = NSFetchRequest(entityName: "Venue") fetchRequest.resultType = .DictionaryResultType //2 创建表达式描述,指定个名称,以便将来在fetch的结果(字典类型)中找到 let sumExpressionDesc = NSExpressionDescription() sumExpressionDesc.name = "sumDeals" //3 创建具体的表达式 sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments:[NSExpression(forKeyPath: "specialCount")]) sumExpressionDesc.expressionResultType = .Integer32AttributeType //4 设置fetch request 将要fetch sum fetchRequest.propertiesToFetch = [sumExpressionDesc] //5 执行,最后通过之前设置的表达式name得到最终结果 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)") } } ~~~ 现在还剩一种.ManagedObjectIDResultType类型,他返回一个包含NSManagedObjectID对象的数组,NSManagedObjectID 可以看成是managed object通用唯一标识,除了多线程的某些场景下,否则很少用到它。 关于性能优化CoreData通过以下几种方式来削减income data: ~~~ ①.fetch batches,可以使用fetchBatchSize, fetchLimit 和 fetchOffset这些属性来削减income data。 ②.支持**faulting**这样的*占位符*。 ③.使用predicates ~~~ 最后要注意一下如果想要在运行时改变predicate,就不能再用model来初始化fetchRequest了 ~~~ override func viewDidLoad() { super.viewDidLoad() // fetchRequest = coreDataStack.model.fetchRequestTemplateForName("FetchRequest") fetchRequest = NSFetchRequest(entityName: "Venue") fetchAndReload() } ~~~ 至于predicates的用法请查看[官方文档](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html) ### **五、Sorting fetched results** 排序主要用到了NSSortDescriptor这个类,值得注意的是该排序是真正发生在**SQLite层面**的,而不是在内存中的,因此该排序十分高效。 NSSortDescriptor实例初始化需要三个参数:①.你要排序属性的key path ②.指明升序 or 降序 ③. 一个可选的selector 这里要注意一点就是CoreData不支持block-based API定义NSSortDescriptor,以及基于block-based方式定义NSPredicate。 ~~~ //下面两个初始化方法不能用于CoreData中 + (instancetype)sortDescriptorWithKey:(NSString *)key ascending:(BOOL)ascending comparator:(NSComparator)cmptr + (NSPredicate *)predicateWithBlock:(BOOL (^)(id evaluatedObject, NSDictionary *bindings))block ~~~ 下面创建了一个按名字排序**filter** ~~~ lazy var nameSortDescriptor: NSSortDescriptor = { var sd = NSSortDescriptor(key: "name", ascending: true, selector: "localizedStandardCompare:") return sd }() ~~~ 这里注意一下第三个参数*localizedStandardCompare*,传入这个参数就会按本地语言排序,这也是苹果官方推荐的一种做法。 得到一个反转排序的sortDescriptor也很简单,这样就可以了 ~~~ selectedSortDescriptor = nameSortDescriptor.reversedSortDescriptor ~~~ ### **六、Asynchronous fetching** iOS 8苹果推出全新的API来处理异步fetch问题,**NSAsynchronousFetchRequest**,这里不要被他的名字迷惑了,和NSFetchRequest没什么直接关系,他其实是**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*看做是对fetchRequest的一种包装,第一个参数是标准的NSFetchRequest 对象,而第二个参数是一个**a completion handler**,不过仅仅有completion handler是不够的,最后还是需要*executeRequest*,下面是一个完整的例子: ~~~ //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一旦执行立即返回一个*NSAsynchronousFetchResult*类型的结果,在这里你不用操心fetch到的数据和UI匹配,这些工作都在第二步handle那里处理了。另外NSAsynchronousFetchResult是有cancel()方法的。 仅仅有新API还不够,还要修改context ~~~ context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType) ~~~ > 另外一种*.PrivateQueueConcurrencyType*将在后面多线程介绍 因为是异步fetch,所以可能tableview初始化完毕后,fetch的结果才到,这里给数据源可以设置一个空数组来fix。 ### **七、Batch updates: no fetching required** 有的时候你需要一次更新很多属性,全部fetch到内存显然是不高效的,iOS 8推出了全新的批量更新(batch updates)来解决这一痛点。这个类就是**NSBatchUpdateRequest**,和上面提到的*NSAsynchronousFetchRequest*都是**NSPersistentStoreRequest**的子类,下面是这个类的定义: ~~~ @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]? } ~~~ 具体使用起来也很简单: ~~~ 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)") } ~~~ 最后注意一点,就是批量更新跳过了NSManagedObjectContext,直接对persistentStore进行更新,没有经过有效性验证,这个就要靠你自己确保更新的数据合法了。