这其实是WWDC2015Session220的学习笔记,顺便整理了一下CoreData批量操作和聚合操作的技巧。批处理CoreData将数据库封装成一个“对象图(objectgraph)”,虽然对于面向对象编程来说有管理继承和Model之间关系的便利,但也牺牲了性能。比如在批量操作中,需要将每条记录作为一个NSManagedObject对象读入内存,修改后存入数据库。但是,使用SQL语句执行既方便又高效。所以苹果在iOS8发布的时候做了“批量更新”,在iOS9发布的时候做了“批量删除”。这两个“新技术”都是直接操作持久层Database,然后需要手动更新/删除内存中的context,这样我们的UI才不会从context中读取内容出错。这样做的好处是省去了对内存的一次写操作和一次查找操作,直接跳过context操作持久层,我们需要手动将持久层的变化结果(BatchResult)重写到context中。只有在需要更新/删除大量数据时才需要这两种技术。但是苹果并没有提供我给出的CoreData处理海量数据的“BatchUpdates”的用法和例子。看完WWDC2015Session220,我觉得“BatchDeletions”在用法上应该和“BatchUpdates”差不多,是坑爹的。PS:我在iOS9的“批量更新”测试中发现了一个bug。每次更新上下文都会漏掉一条记录,这让我很郁闷。聚合操作说完批处理操作,再来说说聚合操作。SQL语法中有一类聚合函数,如count()、sum()、max()、min()、avg()等,它们一般和groupby甚至having一起使用。然而,在称为“对象图”的CoreData中,这种聚合操作是在NSFetchRequest中进行的有替代品。以下示例摘自COREDATAANDAGGREGATEFETCHESINSWIFT:我们要计算每个产品线的销售量和退货量,可以使用以下SQL语句完成:SELECTProductLine,SUM(Sold)asSoldCount,SUM(Returned)asReturnedCountFROMProductsGROUPBYProductLineNSFetchRequest有个propertiesToGroupBy属性,正好对应着groupby语句://Buildoutourfetchrequesttheusualwayletrequest=NSFetchRequest(entityName:self.entityName)//Thisisthecolumnwearegroupingby.Noticethisistheonlynonaggregatecolumn.request.propertiesToGroupBy=["productLine"]下面还需要映射SQL语句Aggregatefunctionsand他们的计算结果,我们需要用NSExpressionDescription和NSExpression来代替SQL中的ProductLine,SUM(Sold)asSoldCount,SUM(Returned)asReturnedCount://CreateanarrayofAnyObjectsinceitneedstocontainmultipletypes--stringsand//NSExpressionDescriptionsvarexpressionDescriptions=[AnyObject]()//WewantproductLinetobeoneofthecolumnsreturned,sojustadditasastringexpressionDescriptions.append("productLine")//为我们的SoldCount列创建表达式描述nvarexpressionDescription=NSExpressionDescription()//NamethecolumnexpressionDescription.name="SoldCount"//使用表达式来指定我们想要采取什么聚合操作和//在哪个列上。InthiscasesumonthesoldcolumnexpressionDescription.expression=NSExpression(format:"@sum.sold")//指定returntypeweexpectexpressionDescription.expressionResultType=.Integer32AttributeType//AppendthedescriptiontoourarrayexpressionDescriptions.append(expressionDescription)//CreateanexpressiondescriptionforourReturnedCountcolumnexpressionDescription=NSExpressionDescription()//命名列expressionDescription.name="ReturnedCount"//使用表达式来指定我们要采取什么聚合操作和//在哪个列上。在这种情况下sumonthereturnedcolumnexpressionDescription.expression=NSExpression(format:"@sum.returned")//指定返回类型weexpectexpressionDescription.expression3resultType=Attribute.InthiscasesumonthereturnedcolumnexpressionDescription.expression=NSExpression(format:"@sum.returned")附加(表达式描述ion)NSExpressionDescription用于表示爬取结果中实体中不存在的列名。比如我们这次使用的聚合函数计算出来的结果,在实体中找不到对应的列,所以我们要给它起一个新的名字,相当于SQL中的as,对应NSExpressionDescription的name属性。聚合函数表达式需要用NSExpression对象表示,比如NSExpression(format:"@sum.returned")就是对“returned”这一列求和。像本例中那样初始化NSExpression需要熟悉格式化语法(例如“@sum.returned”)。初学者建议看官方例子,用通俗易懂的构造方法一步步拼凑自己想要的东西。结果:CoreDataProgrammingGuide将以上三个“列描述”依次添加到expressionDescriptions数组中,最后赋值给NSFetchRequest的propertiesToFetch属性//HandofourexpressiondescriptionstothepropertiesToFetchfield.Expressedasstrings//theseare["productLine","SoldCount","ReturnedCount"]whereproductLineisthevalue//wearegroupingby.request.propertiesToFetch=expressionDescriptionspropertiesToFetch属性实际上是一个NSPropertyDescription类型的数组,可以表示属性、一对一关系和表达式。既然是大杂烩,NSPropertyDescription还有一些子类:NSAttributeDescription、NSExpressionDescription、NSFetchedPropertyDescription、NSRelationshipDescription。我们这里使用的是NSExpressionDesc撕裂。在设置propertiesToFetch属性之前,必须先设置NSFetchRequest的entity属性,否则会抛出NSInvalidArgumentException类型的异常。并且只有当resultType类型设置为NSDictionaryResultType时才会生效//Specifywewantdictionariestobereturnedrequest.resultType=.DictionaryResultType最终结果:[["SoldCount":48,"productLine":Bowler,"ReturnedCount":4],["SoldCount":142,"productLine":Stetson,"ReturnedCount":27],["SoldCount":50,"productLine":TopHat,"ReturnedCount":6]]WWDC2015CoreData的一些新特性苹果号称超过40万个APP使用CoreData,让开发者少写50%~70%的代码。并且在内存性能方面强调出色的内存扩展和主动懒加载,展示了其良好的与UI的绑定机制,并提供了多种多次写入的合并策略。不过这也不能阻止开发者对CoreData的吐槽,毕竟建立在persistent层之上的“objectgraph”不可能像SQL那么全面,所以今年CoreData新的API更像是查漏补缺填补空白,并没有带来重大的功能更新。NSManagedObject有一个新的APIhasPersistentChangedValuesvarhasPersistentChangedValues:Bool{get}使用这个属性来判断NSManagedObject的值是否与“persistentstore”相同.objectIDsForRelationshipNamedfuncobjectIDsForRelationshipNamed(_key:String)->[NSManagedObjectID]适用于大量的多对多关系。由于我们不想将整个关系网络加载到内存中,因此此方法仅返回关联的ID。这是一个例子:letrelations=person.objectIDsForRelationshipNamed("family")letfetchFamily=NSFetchRequest(entityName:"Person")fetchFamily.fetchBatchSize=100fetchFamily.predicate=NSPredicate(format:"selfIN%@",relations)letbatchedRelations=managedObjectContext.executeFetchRequest(fetchFamily)forrelativeinbrowled0/sworkreltimedata/s根据给定的关系名“family”获取对应的ID,一次遍历100行记录,实现了内存占用的可控性。#p#NSManagedObjectContext添加APIrefreshAllObjectsfuncrefreshAllObjects()顾名思义,其功能是刷新上下文中的所有对象,但会保留未保存的更改。与reset方法相比,它仍然会保留NSManagedObject对象的有效性,我们不需要重新抓取任何对象。正因为如此,它非常适合打破一些因遍历双向关系循环而导致的保留循环。mergeChangesFromRemoteContextSaveclassfuncmergeChangesFromRemoteContextSave(_changeNotificationData:[NSObject:AnyObject],intoContextscontexts:[NSManagedObjectContext])在store中使用多个协调器时,该方法会收到一个协调器的通知,并将其应用到另一个在协调员的上下文中。这样我们就可以在所有的上下文中存储最新的数据,而CoreData会维护所有的上下文。shouldDeleteInaccessibleFaultsvarshouldDeleteInaccessibleFaults:BoolCoreData偶尔会抛出异常,但是CoreData无法加载故障,因为它的active对象的懒加载只保留了一部分对象内存中的图形。所以很有可能我在遍历关系的时候会尝试回磁盘找,但是这个时候对象已经被删除了。于是shouldDeleteInaccessibleFaults属性应运而生。默认值为是。如果我们在某处遇到故障,我们会将其标记为已删除。任何缺失的属性都将被设置为null、nil或0。这允许我们的应用程序继续运行并考虑失败对象已被删除。这样程序就不会再崩溃了。NSPersistentStoreCoordinator添加了新的API。之所以添加这两个新的API,是因为很多开发者绕过CoreDataAPI,直接操作底层的数据库文件。因为NSFileManager和POSIX都是对数据库不友好的,如果此时不关闭文件打开的连接,文件就会损坏。destroyPersistentStoreAtURLfuncdestroyPersistentStoreAtURL(_url:NSURL,withTypestoreType:String,optionsoptions:[NSObject:AnyObject]?)throws传入的options必须和addPersistentStoreWithType方法一样,删除对应类型的persistentstore.replacePersistentStoreAtURLfuncreplacePersistentStoreAtURL(_destinationURL:NSURL,destinationOptionsdestinationOptions:[NSObject:AnyObject]?,withPersistentStoreFromURLsourceURL:NSURL,sourceOptionssourceOptions:[NSObject:AnyObject]?,storeTypestoreType:String)throws和上面的destroy是同一个套路,只是替换而已。如果数据库中不存在目标位置,则此替换相当于复制操作。UniqueConstraints很多时候我们在创建一个对象之前会检查它是否已经存在,如果存在则更新,否则创建对象。这很可能会导致竞争条件。如果多个线程同时执行下面的代码,很可能会创建多个重复的对象:",source)letresults=managedObjectContext.executeFetchRequest(createRequest)if(results.count){//updateit!}else{//createit!}}现在CoreData可以处理这个。我们将属性的值设置为唯一的,类似于SQL中的唯一约束。这适用于电子邮件、电话号码、ISBN等场景。并且不要忘记CoreData的对象图中实体的继承关系是在这里指定的。规定子类会从父类继承到带有Unique约束的属性,更多的属性可以设置为Unique。为实体设置Unique属性非常简单。在Xcode中选中相应实体,打开“DataModelinspector”看到“Constraints”,点击加号添加:ModelCaching这是一个轻量级的数据版本自动迁移方案。它会缓存旧版本的数据创建的NSManagedObject对象会被缓存在store中,并迁移到合适的store。Xcode7中生成的子类自动创建NSManagedObject子类化时,模板代码不再自动填充到对应的实体子类文件中,而是同时创建一个Category(Objective-C文件)或扩展(Swift文件),模板代码自动填充这样做的好处是把我们自己写的代码和Xcode生成的模板代码分开,更容易更新和维护。
