Core Data Programming Guid

()

关于Persistent Stack


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

Persistent stack
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文件。有这样两种方法:

  1. 使用NSManagedObjectModel的initWithContentOfURL:方法。
    这是一种比较普遍使用的方法。
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:modelName withExtension:@"momd"];
    NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
  2. 使用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
指定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
指定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:

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

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

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

  4. 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的类型,分别是:

  1. property层次的validation
  2. property之间的validation

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

在Core Data Model editor中可以设置一些validation逻辑
在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 Model
Copying and Copy and Paste
Drag and Drop
Undo Management
Ensuring Data Is Up-to-Date
Change and Undo Management
Fetched Properties
Non-Standard Persistent Attributes
Associate Metadata With a Store to Provide Additional Information and Support Spotlight Indexing
Core Data and Cocoa Bindings
Change Management
Persistent Store Features
Core Data Performance
Troubleshooting Core Data
Efficiently Importing Data

 

 
posted @ 2015-09-08 17:47  大胖子2015  阅读(174)  评论(0编辑  收藏  举报