Core Data Programming Guid

it2024-10-22  21

(转)

关于Persistent Stack

对象和外部数据存储,这两者之间的媒介,被整体叫做persistence stack。其中,managed object context位于栈顶,persistent object store位于栈底,中间的是persistent store coordinator。

Persistent stack

实际上,是persistent store coordinator决定着这个栈。它使用了facade模式,使得栈底的多个persistent store,在呈现给context的时候,就像一个整体一样。一个coordinator只能和一个managed object model相关联。


关于Managed Object Model

一个managed object model是NSManagedObjectModel类的实例。它描述了第三方app中需要使用到的一系列entity,和多个entity之间的关系。一个model中可能有很多NSEntityDescription对象来代表这个model的各个entity。对于每个entity来说,有两个很重要的特性,一个是这个entity的名字,另一个是在运行时,表示这个entity的类的名字。

一个entity可能会有attribute、relationship,也可能有fetched property,这三者统称为property。需要注意的是,property不能和NSObject或NSManagedObject已有的方法名重叠,比如,不能给某个property起名为“description”。比较特殊的一种property叫做transient property,它是不会被保存到persistent store中去的。

多个entity之间可能会有继承关系,也可能某个entity会被指定为抽象的。

大多数model中的元素(比如entity、attribute、relationship)都会有一个对应的user info。


创建一个model

使用Xcode创建model

在Xcode中,选择File->New->File->Core Data->Data Model就可以创建一个扩展名为.xcdatamodeld的“源文件”了(实际上应该是一个目录)。其中包含了一个扩展名为.xcdatamodel的“源文件”。可以使用Xcode的Core Data model editor,在xcdatamodel文件中编辑model的内容,比如其中包含什么样的entity,每个entity中有什么样的attribute,以及各个entity之间的关系,等等。

如果App更新时,需要对model进行改动,就需要创建一个新的model version。在Xcode中,选中xcdatamodeld,选择Editor->Add Model Version,可以继续创建其中的xcdatamodel“源文件”。

除了model中关于entity和property的各种信息,xcdatamodel还会包含一些其他信息,比如绘制的图表的宽高排列之类的,但这些信息在运行时并没有什么意义。所以,model文件的编译工具momc会把运行时没有意义的信息去掉,将xcdatamodel文件编译成mom文件,将xcdatamodeld目录编译成momd目录。

在Xcode中找到编译好的.app文件,右键Show in Finder,打开里面的内容后,可以看到其中的.momd文件夹,和这个文件夹里面的.mom文件。

如果写的是iOS上的app,则在需要程序员自己加载model文件。有这样两种方法:

使用NSManagedObjectModel的initWithContentOfURL:方法。这是一种比较普遍使用的方法。 NSURL *modelURL = [[NSBundle mainBundle] URLForResource:modelName withExtension:@"momd"]; NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 使用mergedModelFromBundles:方法.如果参数是nil,则会搜索main bundle,把其中的所有model给merge起来。
在代码中创建\修改model

在model被一个managed object context或者一个persistent store coordinator使用之前,这个model是可以在代码中被修改的。这允许程序员动态的创建或修改model。

试了一下在代码中创建model:

NSManagedObjectModel *model = [[NSManagedObjectModel alloc] init]; NSEntityDescription *launchInfoEntity = [[NSEntityDescription alloc] init]; [launchInfoEntity setName:@"LaunchInfo"]; NSAttributeDescription *dateAttribute = [[NSAttributeDescription alloc] init]; [dateAttribute setName:@"date"]; [dateAttribute setAttributeType:NSDateAttributeType]; [dateAttribute setOptional:NO]; [launchInfoEntity setProperties:@[dateAttribute]]; [model setEntities:@[launchInfoEntity]];

如果model是在被一个managed object context或者一个persistent store coordinator使用之后,受到改动,则会抛出exception:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Can't modify an immutable model.'

Fetch Request Template

程序员可以使用NSFetchRequest类来描述从持久化存储中取得一些对象的请求。在实际的开发中,同样或相似的请求往往会被执行多次,所以,程序员可以自定义一些fetch request template,并把它们存到model中。可以使用Xcode的Core Data model editor,也可以在代码中定义。

使用Core Date model editor定义fetch request template

Editor->Add FetchRequest来新建一个fetch request。

填写Predicate,可以使用变量。右边栏还可以指定一些高级选项。

指定Predicate

在需要使用时,只要在代码中取出对应的fetch request template:

NSManagedObjectModel *managedObjectModel = [[context persistentStoreCoordinator] managedObjectModel]; NSFetchRequest *fetchRequest = [managedObjectModel fetchRequestFromTemplateWithName:@"fetchLaunchInfoBeforeSomeDate" substitutionVariables:@{@"DATE" : [NSDate date]}]; NSArray *fetchResult = [context executeFetchRequest:fetchRequest error:&error];

就可以正常使用了。

直接在代码中创建fetch request template

也可以完全动态的创建fetch request template:

NSManagedObjectModel *managedObjectModel = [[context persistentStoreCoordinator] managedObjectModel]; NSFetchRequest *fetchRequestTemplate = [[NSFetchRequest alloc] initWithEntityName:@"LaunchInfo"]; [fetchRequestTemplate setPredicate:[NSPredicate predicateWithFormat:@"date > $DATE"]]; [managedObjectModel setFetchRequestTemplate:fetchRequestTemplate forName:@"fetchLaunchInfoAfterSomeDate"];

关于Configuration

如果程序员想要把不同的entity存放到不同的persistent store中去,应该怎么做呢?一个coordinator只能对应一个managed object model,所以在默认情况下,每一个与这个coordinator相关联的persistent store,都存放了同样的entity。为了避免这样的限制,可以使用Configuration来指定每个persistent store中应该存放哪些entity。指定了Configuration之后,当程序员取这些对象的时候,它们会自动从不同的文件中被取出;保存时,它们也会被自动保存到不同的文件。

一个configuration由名字和若干entity组成。可以在代码中用

setEntities:forConfiguration:

方法动态的定义configuration;

也可以在Core Data editor tool中定义:

指定Configuration

每当给coordinator增加persistent store的时候,只用在configuration参数中指定对应的configuration即可以使用:

if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:@"ExitInfoConfiguration" URL:exitInfoStoreURL options:nil error:&error]) { //Handle error } if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:@"LaunchInfoConfiguration" URL:launchInfoStoreURL options:nil error:&error]) { //Handle error }

关于Managed Object

一个managed object代表的是一个entity的实例。

每个managed object与一个managed object context相关联。在一个特定的context中,持久化存储中的一个特定的记录,只能有一个对应的managed object,这种技术叫做Uniquing。但是,也可能有多个context,每个context都持有一个表示同一条记录的managed object。


关于accessor方法

可以使用Xcode根据xcdatamodel中的内容自动生成NSManagedObject的子类。在子类的实现中,我们能看到,property被@dynamic修饰了。那是因为Core Data会在运行时动态生成accessor方法,这样生成的accessor方法是比较高效的,也就是说,程序员一般不需要写自定义的accessor方法。

也可以通过key-value的形式来获取或设置attributes的值,但是在性能上KVC不如accessor方法,所以只应该在必要的情况下使用。

如果这个managed object有to-many relationship,很多时候,程序员可能会需要增添、删除或改动这个to-many relationship中的某几个元素,这个时候则应该使用mutableSetValueForKey:方法或者动态生成的relationship mutator方法。


关于Managed Object的生命周期

一个managed object的生命周期和标准的Cocoa对象的生命周期不太一样,因为那是由Core Data来管理的。一个managed object表示的数据的生命周期,和这个manged object的实例的生命周期是独立的。

可以通过一个managed object得到它所在的context,也可以通过一个context得到其中的managed object。但是默认情况下,managed object和context之间的引用是弱引用。然而有一种例外情况,context会对“被改动过的”managed object持强引用,这里的改动包括插入、删除和修改,直到context被save、reset或者rollback。同时,undo manager也会用强引用来维持被改动过的managed object。可以用setRetainsRegisteredObjects:方法改变这种默认情况,使得context对managed object持强引用。

当managed object有relationship的时候,它会对这个关联的对象持强引用,这也意味着可能有强引用循环出现。所以,当使用完一个managed object的时候,应该用refreshObject:mergeChanges:方法让它成为一个fault。

在一个managed object被创建的时候,其中每个property的值是在对应的entity中的default value。如果需要做一些自定义的初始化,建议重写:awakeFromInsert或者awakeFromFetch方法。

其中,awakeFromInsert会在调用了initWithEntity:insertIntoManagedObjectContext:或者insertNewObjectForEntityForName:inManagedObjectContext:方法之后立刻被调用。所以,重写这个方法,主要是可以为managed object中的property提供特殊的默认值,比如这个对象被创建的时间。

awakeFromFetch方法会在managed object从一个持久化存储中被取出来的时候调用。重写这个方法,可以用于建立transient值和缓存。需要注意的是,如果在这个方法中,改变了managed object中某些property,context不会被认为是dirty的。这也就意味着不应该在这个方法中操纵relationship,因为目标对象不会为此做出应有的改变。

initWithEntity:insertIntoManagedObjectContext: 这个方法也可以重写,但是并不鼓励这样做。因为在重写的这个方法中改变的状态,可能会不支持undo和redo。

在需要“析构”的时候,不应该重写dealloc方法,而是应该重写didTurnInfoFault方法。这个方法会在managed object变成fault的时候被调用,也就是说会比真正的析构早一些。


关于Relationship

大多数的relationship天生就是双向的(一个主要的例外就是fetched property)。一般来说,在使用Core Data的时候,也应该为relationship指定反向关系,这样可以确保object graph的一致性。

一个relationship是有delete rule的。这指定了当这个对象即将被删除的时候应该发生的行为。有这样几种delete rule:

Deny如果至少有一个relationship的目的对象存在,源对象是不能被删除的;

Nullify在删除当前对象的同时,将relationship的目的对象的反向关系设置为null;

Cascade在删除当前对象的同时,也删除relationship的目的对象;

No Action在删除当前对象的同时,对relationship的目的对象不做任何操作。在使用这个delete rule的时候,程序员有责任自行维护object graph,所以应该将对应的反向关系设置成有意义的值。


关于Object ID

一个NSManagedObjectID对象是managed object的全局ID。Object ID有临时和持久之分。当一个managed object刚刚被创建时,它将获得一个临时的object ID;只有当它被保存到持久化存储中时,它才会被赋予一个持久的ID。

Object ID也可以被转化成URI。可以使用 managedObjectIDForURIRepresentation:方法或objectWithID:方法通过URI或ID获取对应的managed object。


关于Validation

Validation机制用于检验managed object的property的值是否满足一定条件。有两种validation的类型,分别是:

property层次的validationproperty之间的validation

Core Data允许程序员在managed object model中设定简单的validation逻辑。比如,可以设置数字和日期的最大最小值,可以设置字符串的最大最小长度、需要匹配的正则表达式,还可以设置to-many relationship中数目的最大最小值。

在Core Data Model editor中可以设置一些validation逻辑

除了可以对model设置这些validation逻辑,还可以在代码中进行自定义。

如果想要自定义property层次的validation,程序员不应该重写validateValue:forKey:error:方法,而是应该实现validate<Key>:error:方法。然而,如果想要自行检查某个property是否符合规定,应该调用的是validateValue:forKey:error:方法,这个方法会将定义在managed object model中的validation逻辑也考虑进去。

也可以自定义property之间的validation。这可以通过重写validateForUpdate:、validateForInsert:和validateForDelete:方法来实现。在重写的这三个方法中,应该首先调用父类的实现。

所有的validation限制都只有在保存操作的过程中会被应用。因为managed object context的本意就是一块草稿板,所以应该允许其中的对象有临时性的“不合理”。


关于Faulting

一个managed object通常会用于表示被持久化存储的数据,但是在有些情况下,一个managed object可能是fault的,也就是说它的property还没有从外部数据存储中载入进来。这是Core Data用于减少内存占用的一种机制。

当访问到一个managed object的某个持久化的property的时候,fault被触发了,如果内存中的cache没有被击中的话,数据会被自动从持久化存储中取过来,这里的开销是比较昂贵的。

需要注意的是,description方法是不会触发fault的,所以打印刚刚取出来的managed object可以看到“<fault>”字样。比如这样:

"<LaunchInfo: 0x10060b450> (entity: LaunchInfo; id: 0x40000b <x-coredata://4973AB39-0CD8-4480-AA07-7A3A877BE87D/LaunchInfo/p1> ; data: <fault>)"

如果重写description方法,并在其中访问了某个持久化的property,则fault会被触发。所以应该尽量避免这样的做法。

可以使用refreshObject:mergeChanges:并传人参数no让一个managed object变成fault。但是必须保证其中的relationship没有被改变。


关于Fetching

取得指定的对象

如果app使用了多个context,那么程序员可能就需要测试一个对象是否已经从persistent store中被删除了。这时,可以创建一个fetch request,其中这样指定predicate:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self == %@", targetObject];

这样就可以通过判断fetch到的对象的数目是否为0来判断目标对象是否已被删除。其中的targetObject可以是一个managed object,也可以是一个manged object ID。如果一次需要测试多个目标对象是否被删除,可以使用更高效的IN操作符:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self IN %@", arrayOfManagedObjectIDs];
获取特定的值

有的时候,程序员可能不需要获取整个managed object,而是只是需要其中的某个attribute。NSExpressionDescription可以帮助程序员取得需要的值。这时,需要使用setResultType:方法来指定这个fetch返回的结果类型是NSDictionaryResultType;还需要创建NSExpressionDescription的实例,来指定哪些property是需要取得的。官方文档里有示例代码,偷个懒。


还欠缺的部分

这篇博客真是拖着写了好久。但是还有好多内容没有理解,因为偷懒+之前在工作中对这些部分接触不多没什么感受,所以先放在这里,等下一遍看的时候,再慢慢理解好了。

Localizing a Managed Object ModelCopying and Copy and PasteDrag and DropUndo ManagementEnsuring Data Is Up-to-DateChange and Undo ManagementFetched PropertiesNon-Standard Persistent AttributesAssociate Metadata With a Store to Provide Additional Information and Support Spotlight IndexingCore Data and Cocoa BindingsChange ManagementPersistent Store FeaturesCore Data PerformanceTroubleshooting Core DataEfficiently Importing Data

 

 

转载于:https://www.cnblogs.com/ylg-----/p/4792461.html

相关资源:数据结构—成绩单生成器
最新回复(0)