天天看点

深入浅出 Cocoa 之 Core Data

原文网址:http://www.cppblog.com/ipzyh/articles/CoreData.html

Core data 是 Cocoa 中处理数据,绑定数据的关键特性,其重要性不言而喻,但也比较复杂。Core Data 相关的类比较多,初学者往往不太容易弄懂。计划用三个教程来讲解这一部分:

框架详解:讲解  Core data 框架,运作过程,设计的类;

Core data应用程序示例:通过生成一个使用 Core data 的应用程序来讲解如何 在  XCode 4 中使用 Core data。

手动创建Core data示例:不利用框架自动生成代码,完全自己编写所有的 Core data 相关代码的命令行应用程序来深入讲解 Core data的使用。

本文为第一部份:框架详解

一,概观

下面先给出一张类关系图,让我们对它有个总体的认识。

深入浅出 Cocoa 之 Core Data

在上图中,我们可以看到有五个相关模块:

1, Managed Object Model

Managed Object Model 是描述应用程序的数据模型,这个模型包含实体(Entity),特性(Property),读取请求(Fetch Request)等。(下文都使用英文术语。)

2,Managed Object Context

Managed Object Context 参与对数据对象进行各种操作的全过程,并监测数据对象的变化,以提供对 undo/redo 的支持及更新绑定到数据的 UI。

3,Persistent Store Coordinator

Persistent Store Coordinator 相当于数据文件管理器,处理底层的对数据文件的读取与写入。一般我们无需与它打交道。

4,Managed Object

Managed Object 数据对象,与 Managed Object Context 相关联。

5,Controller

图中绿色的 Array Controller, Object Controller, Tree Controller 这些控制器,一般都是通过 control+drag 将 Managed Object Context 绑定到它们,这样我们就可以在 nib 中可视化地操作数据。

这写模块是怎样运作的呢?

深入浅出 Cocoa 之 Core Data

1,应用程序先创建或读取模型文件(后缀为xcdatamodeld)生成 NSManagedObjectModel 对象。Document应用程序是一般是通过 NSDocument 或其子类 NSPersistentDocument)从模型文件(后缀为 xcdatamodeld)读取。

2,然后生成 NSManagedObjectContext 和 NSPersistentStoreCoordinator 对象,前者对用户透明地调用后者对数据文件进行读写。

3,NSPersistentStoreCoordinator 负责从数据文件(xml, sqlite,二进制文件等)中读取数据生成 Managed Object,或保存 Managed Object 写入数据文件。

4,NSManagedObjectContext 参与对数据进行各种操作的整个过程,它持有 Managed Object。我们通过它来监测 Managed Object。监测数据对象有两个作用:支持 undo/redo 以及数据绑定。这个类是最常被用到的。

5,Array Controller, Object Controller, Tree Controller 这些控制器一般与 NSManagedObjectContext 关联,因此我们可以通过它们在 nib 中可视化地操作数据对象。

二, Model class

模型有点像数据库的表结构,里面包含 Entry, 实体又包含三种 Property:Attribute(属性),RelationShip(关系), Fetched Property(读取属性)。Model class 的名字多以 "Description" 结尾。我们可以看出:模型就是描述数据类型以及其关系的。

主要的 Model class 有:

Model Classes

Managed Object Model NSManagedObjectModel 数据模型
Entity NSEntityDescription 抽象数据类型,相当于数据库中的表
Property NSPropertyDescription Entity 特性,相当于数据库表中的一列
  > Attribute NSAttributeDescription 基本数值型属性(如Int16, BOOL, Date等类型的属性)
  > Relationship NSRelationshipDescription 属性之间的关系
  > Fetched Property NSFetchedPropertyDescription 查询属性(相当于数据库中的查询语句)

1)Entity - NSEntityDescription

Entity 相当于数据库中的一个表,它描述一种抽象数据类型,其对应的类为 NSManagedObject 或其子类。

NSEntityDescription 常用方法:

+insertNewObjectForEntityForName:inManagedObjectContext: 工厂方法,根据给定的 Entity 描述,生成相应的 NSManagedObject 对象,并插入 ManagedObjectContext 中。

-managedObjectClassName 返回映射到 Entity 的 NSManagedObject 类名

-attributesByName 以名字为 key, 返回 Entity 中对应的 Attributes

-relationshipsByName 以名字为 key, 返回 Entity 中对应的 Relationships

2)Property - NSPropertyDescription

Property 为 Entity 的特性,它相当于数据库表中的一列,或者 XML 文件中的 value-key 对中的 key。它可以描述实体数据(Attribute),Entity之间的关系(RelationShip),或查询属性(Fetched Property)。

 > Attribute - NSAttributeDescription

Attribute 存储基本数据,如 NSString, NSNumber or NSDate 等。它可以有默认值,也可以使用正则表达式或其他条件对其值进行限定。一个属性可以是 optional 的。

 > Relationship - NSRelationshipDescription 

Relationship 描述 Entity,Property 之间的关系,可以是一对一,也可以是一对多的关系。 

 > Fetched Property - NSFetchedPropertyDescription

Fetched Property 根据查询谓词返回指定 Entity 的符合条件的数据对象。

上面说的比较抽象,举个例子来说,见图:

深入浅出 Cocoa 之 Core Data

我们有一个 CocoaDataDemo.xcdatamodeld 模型文件,应用程序根据它生成一个 NSManagedObjectModel 对象,这个模型有三个 Entity,每个 Entity 又可包含 Attribute Relationship, Feteched Property 三种类型的 Property。在本例中, Author Entity 包含两个Attribute : name 和 email,它们对于的运行时类均为 NSManagedObject;还包含一个与 Post 的 Relationship;没有设置  Feteched Property。

我们通常使用 KVC 机制来访问 Property。下面来看代码:

NSManagedObjectContext * context = [[NSApp delegate] managedObjectContext];
NSManagedObject        * author  = nil;
author = [NSEntityDescription insertNewObjectForEntityForName: @"Author" inManagedObjectContext: context];
[author setValue: @"[email protected]" forKey: @"email"];
NSLog (@"The Author's email is: %@", [author valueForKey:@"email"]);
           

在上面代码中,我们先取得 NSManagedObjectContext, 然后调用 NSEntityDescription 的方法,以 Author 为实体模型,生成对应的 NSManagedObject 对象,插入 NSManagedObjectContext 中,然后给这个对象设置特性 email 的值。

三,运行时类与对象

> Managed Object - NSManagedObject

Managed Object 表示数据文件中的一条记录,每一个 Managed Object 在内存中对应 Entity 的一个数据表示。Managed Object 的成员为 Entity 的 Property 所描述。

比如在上面的代码,author 这个 NSManagedObject,对应名为 Author 的 Entity。

每一个 Managed Object 都有一个全局 ID(类型为:NSManagedObjectID)。Managed Object 会附加到一个 Managed Object Context,我们可以通过这个全局 ID 在 Managed Object Context 查询对应的 Managed Object。

NSManagedObject 常用方法

-entity 获取其 Entity
-objectID 获取其 Managed Object ID
-valueForKey: 获取指定 Property 的值
-setValue: forKey: 设定指定 Property 的值

> Managed Object Context - NSManagedObjectContext

Managed Object Context 的作用相当重要,对数据对象进行的操作都与它有关。当创建一个数据对象并插入 Managed Object Context 中,Managed Object Context 就开始跟踪这个数据对象的一切变动,并在合适的时候提供对 undo/redo 的支持,或调用 Persistent Store Coordinato 将变化保存到数据文件中去。

通常我们将 controller 类(如:NSArrayController,NSTreeController)或其子类与 Managed Object Context 绑定,这样就方便我们动态地生成,获取数据对象等。

NSManagedObjectContext 常用方法

-save: 将数据对象保存到数据文件
-objectWithID: 查询指定 Managed Object ID 的数据对象
-deleteObject: 将一个数据对象标记为删除,但是要等到 Context 提交更改时才真正删除数据对象
-undo 回滚最后一步操作,这是都 undo/redo 的支持
-lock 加锁,常用于多线程以及创建事务。同类接口还有:-unlock and -tryLock
-rollback 还原数据文件内容
-reset 清除缓存的 Managed Objects。只应当在添加或删除 Persistent Stores 时使用
-undoManager 返回当前 Context 所使用的 NSUndoManager
-assignObject: toPersistantStore:

由于 Context 可以管理从不同数据文件而来的数据对象,

这个接口的作用就是指定数据对象的存储数据文件(通过指定 PersistantStore 实现)

-executeFetchRequest: error: 执行 Fetch Request 并返回所有匹配的数据对象

> Persistent Store Coordinator - NSPersistentStoreCoordinator

使用 Core Data document 类型的应用程序,通常会从磁盘上的数据文中中读取或存储数据,这写底层的读写就由 Persistent Store Coordinator 来处理。一般我们无需与它直接打交道来读写文件,Managed Object Context 在背后已经为我们调用 Persistent Store Coordinator 做了这部分工作。

NSPersistentStoreCoordinator 常用方法

-addPersistentStoreForURL:configuration:URL:options:error: 装载数据存储,对应的卸载数据存储的接口为 -removePersistentStore:error:
-migratePersistentStore:toURL:options:withType:error:

迁移数据存储,效果与 "save as"相似,但是操作成功后,

迁移前的数据存储不可再使用

-managedObjectIDForURIRepresentation: 返回给定 URL所指示的数据存储的 object id,如果找不到匹配的数据存储则返回 nil
-persistentStoreForURL: 返回指定路径的 Persistent Store
-URLForPersistentStore: 返回指定 Persistent Store 的存储路径

> Persistent Document - NSPersistentDocument

NSPersistentDocument 是 NSDocument 的子类。 multi-document Core Data 应用程序使用它来简化对 Core Data 的操作。通常使用 NSPersistentDocument 的默认实现就足够了,它从 Info.plist 中读取 Document types 信息来决定数据的存储格式(xml,sqlite, binary)。

NSPersistentDocument 常用方法

-managedObjectContext 返回文档的 Managed Object Context,在多文档应用程序中,每个文档都有自己的 Context。
-managedObjectModel 返回文档的 Managed Object Model

四,Fetch Requests

Fetch Requests 相当于一个查询语句,你必须指定要查询的 Entity。我们通过 Fetch Requests 向 Managed Object Context 查询符合条件的数据对象,以 NSArray 形式返回查询结果,如果我们没有设置任何查询条件,则返回该 Entity 的所有数据对象。我们可以使用谓词来设置查询条件,通常会将常用的 Fetch Requests 保存到 dictionary 以重复利用。

示例:

NSManagedObjectContext * context  = [[NSApp delegate] managedObjectContext];
NSManagedObjectModel   * model    = [[NSApp delegate] managedObjectModel];
NSDictionary           * entities = [model entitiesByName];
NSEntityDescription    * entity   = [entities valueForKey:@"Post"];
NSPredicate * predicate;
predicate = [NSPredicate predicateWithFormat:@"creationDate > %@", date];
NSSortDescriptor * sort = [[NSortDescriptor alloc] initWithKey:@"title"];
NSArray * sortDescriptors = [NSArray arrayWithObject: sort];
NSFetchRequest * fetch = [[NSFetchRequest alloc] init];
[fetch setEntity: entity];
[fetch setPredicate: predicate];
[fetch setSortDescriptors: sortDescriptors];
NSArray * results = [context executeFetchRequest:fetch error:nil];
[sort release];
[fetch release];
           

在上面代码中,我们查询在指定日期之后创建的 post,并将查询结果按照 title 排序返回。

NSFetchRequest 常用方法

-setEntity: 设置你要查询的数据对象的类型(Entity)
-setPredicate: 设置查询条件
-setFetchLimit: 设置最大查询对象数目
-setSortDescriptors: 设置查询结果的排序方法
-setAffectedStores: 设置可以在哪些数据存储中查询

参考资料:

Core Data Reference API listing for the Core Data classes

http://developer.apple.com/documentation/Cocoa/Reference/CoreData_ObjC/index.html

NSPredicate Reference API listing for NSPredicate

http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSPredicate.html

2、代码示例

前面详细讲解了 Core Data 的框架以及设计的类,下面我们来讲解一个完全手动编写代码使用这些类的示例,这个例子来自苹果官方示例。在这个例子里面,我们打算做这样一件事情:记录程序运行记录(时间与 process id),并保存到xml文件中。我们使用 Core Data 来做这个事情。

示例代码下载:点击这里

一,建立一个新的 Mac command-line tool application 工程,命名为 CoreDataTutorial。为支持垃圾主动回收机制,点击项目名称,在右边的 Build Setting 中查找 garbage 关键字,将找到的 Objective-C Garbage Collection 设置为 Required [-fobj-gc-only]。并将  main.m 中 的 main() 方法修改为如下:

int  main ( int  argc,  const   char   *  argv[])

{

    NSLog( @"  === Core Data Tutorial === " );

     //  Enable GC

     //

    objc_startCollectorThread();

     return   0 ;

}

二,创建并设置模型类

在 main() 之前添加如下方法:

NSManagedObjectModel  * managedObjectModel()

{

     static  NSManagedObjectModel  * moModel  =  nil;

     if  (moModel  !=  nil) {

         return  moModel;

    }

    moModel  =  [[NSManagedObjectModel alloc] init];

     //  Create the entity

     //

    NSEntityDescription  * runEntity  =  [[NSEntityDescription alloc] init];

    [runEntity setName: @" Run " ];

    [runEntity setManagedObjectClassName: @" Run " ];

    [moModel setEntities:[NSArray arrayWithObject:runEntity]];

     //  Add the Attributes

     //

    NSAttributeDescription  * dateAttribute  =  [[NSAttributeDescription alloc] init];

    [dateAttribute setName: @" date " ];

    [dateAttribute setAttributeType:NSDateAttributeType];

    [dateAttribute setOptional:NO];

    NSAttributeDescription  * idAttribute  =  [[NSAttributeDescription alloc] init];

    [idAttribute setName: @" processID " ];

    [idAttribute setAttributeType:NSInteger32AttributeType];

    [idAttribute setOptional:NO];

    [idAttribute setDefaultValue:[NSNumber numberWithInteger: - 1 ]];

     //  Create the validation predicate for the process ID.

     //  The following code is equivalent to validationPredicate = [NSPredicate predicateWithFormat:@"SELF > 0"]

     //

    NSExpression  * lhs  =  [NSExpression expressionForEvaluatedObject];

    NSExpression  * rhs  =  [NSExpression expressionForConstantValue:[NSNumber numberWithInteger: 0 ]];

    NSPredicate  * validationPredicate  =  [NSComparisonPredicate

                                        predicateWithLeftExpression:lhs

                                        rightExpression:rhs

                                        modifier:NSDirectPredicateModifier

                                        type:NSGreaterThanPredicateOperatorType

                                        options: 0 ];

    NSString  * validationWarning  =   @" Process ID < 1 " ;

    [idAttribute setValidationPredicates:[NSArray arrayWithObject:validationPredicate]

                  withValidationWarnings:[NSArray arrayWithObject:validationWarning]];

     //  set the properties for the entity.

     //

    NSArray  * properties  =  [NSArray arrayWithObjects: dateAttribute, idAttribute, nil];

    [runEntity setProperties:properties];

     //  Add a Localization Dictionary

     //

    NSMutableDictionary  * localizationDictionary  =  [NSMutableDictionary dictionary];

    [localizationDictionary setObject: @" Date "  forKey: @" Property/date/Entity/Run " ];

    [localizationDictionary setObject: @" Process ID "  forKey: @" Property/processID/Entity/Run " ];

    [localizationDictionary setObject: @" Process ID must not be less than 1 "  forKey: @" ErrorString/Process ID < 1 " ];

    [moModel setLocalizationDictionary:localizationDictionary];

     return  moModel;

}

在上面的代码中:

1)我们创建了一个全局模型 moModel;

2)并在其中创建一个名为 Run 的 Entity,这个 Entity 对应的 ManagedObject 类名为 Run(很快我们将创建这样一个类);

3)给 Run Entity 添加了两个必须的 Property:date 和 processID,分别表示运行时间以及进程 ID;并设置默认的进程 ID 为 -1;

4)给 processID 特性设置检验条件:必须大于 0;

5)给模型设置本地化描述词典;

本地化描述提供对 Entity,Property,Error信息等的便于理解的描述,其可用的键值对如下表:

<table cellspacing="0" cellpadding="0" xhe-border"="">

Key

Value

"Entity/NonLocalizedEntityName"

"LocalizedEntityName"

"Property/NonLocalizedPropertyName/Entity/EntityName"

"LocalizedPropertyName"

"Property/NonLocalizedPropertyName"

"LocalizedPropertyName"

"ErrorString/NonLocalizedErrorString"

"LocalizedErrorString"

三,创建并设置运行时类和对象

由于要用到存储功能,所以我们必须定义持久化数据的存储路径,在 main() 之前添加如下方法设置存储路径:

NSURL  * applicationLogDirectory()

{

    NSString  * LOG_DIRECTORY  =   @" CoreDataTutorial " ;

     static  NSURL  * ald  =  nil;

     if  (ald  ==  nil)

    {

        NSFileManager  * fileManager  =  [[NSFileManager alloc] init];

        NSError  * error  =  nil;

        NSURL  * libraryURL  =  [fileManager URLForDirectory:NSLibraryDirectory inDomain:NSUserDomainMask

                                       appropriateForURL:nil create:YES error: & error];

         if  (libraryURL  ==  nil) {

            NSLog( @" Could not access Library directory\n%@ " , [error localizedDescription]);

        }

         else

        {

            ald  =  [libraryURL URLByAppendingPathComponent: @" Logs " ];

            ald  =  [ald URLByAppendingPathComponent:LOG_DIRECTORY];

            NSLog( @"  >> log path %@ " , [ald path]);

            NSDictionary  * properties  =  [ald resourceValuesForKeys:[NSArray arrayWithObject:NSURLIsDirectoryKey] error: & error];

             if  (properties  ==  nil)

            {

                 if  ( ! [fileManager createDirectoryAtPath:[ald path] withIntermediateDirectories:YES attributes:nil error: & error])

                {

                    NSLog( @" Could not create directory %@\n%@ " ,

                          [ald path], [error localizedDescription]);

                    ald  =  nil;

                }

            }

        }

    }

     return  ald;

}

 在上面的代码中,我们将持久化数据文件保存到路径:/Users/kesalin/Library/Logs/CoreDataTutorial 下。

  下面,我们来创建运行时对象:ManagedObjectContext 和 PersistentStoreCoordinator。

NSManagedObjectContext  * managedObjectContext()

{

     static  NSManagedObjectContext  * moContext  =  nil;

     if  (moContext  !=  nil) {

         return  moContext;

    }

    moContext  =  [[NSManagedObjectContext alloc] init];

     //  Create a persistent store coordinator, then set the coordinator for the context.

     //

    NSManagedObjectModel  * moModel  =  managedObjectModel();

    NSPersistentStoreCoordinator  * coordinator  =  [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:moModel];

    [moContext setPersistentStoreCoordinator: coordinator];

     //  Create a new persistent store of the appropriate type. 

     //

    NSString  * STORE_TYPE  =  NSXMLStoreType;

    NSString  * STORE_FILENAME  =   @" CoreDataTutorial.xml " ;

    NSError  * error  =  nil;

    NSURL  * url  =  [applicationLogDirectory() URLByAppendingPathComponent:STORE_FILENAME];

    NSPersistentStore  * newStore  =  [coordinator addPersistentStoreWithType:STORE_TYPE

                                                            configuration:nil

                                                                      URL:url

                                                                  options:nil

                                                                    error: & error];

     if  (newStore  ==  nil) {

        NSLog( @" Store Configuration Failure\n%@ " , ([error localizedDescription]  !=  nil)  ?  [error localizedDescription] :  @" Unknown Error " );

    }

     return  moContext;

}

在上面的代码中:

1)我们创建了一个全局 ManagedObjectContext 对象 moContext;

2)并在设置其 persistent store coordinator,存储类型为 xml,保存文件名为:CoreDataTutorial.xml,并将其放到前面定义的存储路径下。

好,至此万事具备,只欠 ManagedObject 了!下面我们就来定义这个数据对象类。向工程添加 Core Data->NSManagedObject subclass 的类,名为 Run (模型中 Entity 定义的类名) 。

Run.h

//

//   Run.h

//   CoreDataTutorial

//

//   Created by kesalin on 8/29/11.

//   Copyright 2011 [email protected]. All rights reserved.

//

#import  < CoreData / NSManagedObject.h >

@interface Run : NSManagedObject

{

    NSInteger processID;

}

@property (retain) NSDate  * date;

@property (retain) NSDate  * primitiveDate;

@property NSInteger processID;

@end

Run.m

//

//   Run.m

//   CoreDataTutorial

//

//   Created by kesalin on 8/29/11.

//   Copyright 2011 [email protected]. All rights reserved.

//

#import  " Run.h "

@implementation Run

@dynamic date;

@dynamic primitiveDate;

-  ( void ) awakeFromInsert

{

    [super awakeFromInsert];

    self.primitiveDate  =  [NSDate date];

}

#pragma mark  -

#pragma mark Getter and setter

-  (NSInteger)processID 

{

    [self willAccessValueForKey: @" processID " ];

    NSInteger pid  =  processID;

    [self didAccessValueForKey: @" processID " ];

     return  pid;

}

-  ( void )setProcessID:(NSInteger)newProcessID

{

    [self willChangeValueForKey: @" processID " ];

    processID  =  newProcessID;

    [self didChangeValueForKey: @" processID " ];

}

//  Implement a setNilValueForKey: method. If the key is “processID” then set processID to 0.

//

-  ( void )setNilValueForKey:(NSString  * )key {

     if  ([key isEqualToString: @" processID " ]) {

        self.processID  =   0 ;

    }

     else  {

        [super setNilValueForKey:key];

    }

}

@end

注意:

1)这个类中的 date 和 primitiveDate 的访问属性为 @dynamic,这表明在运行期会动态生成对应的 setter 和 getter;

2)在这里我们演示了如何正确地手动实现 processID 的 setter 和 getter:为了让 ManagedObjecContext  能够检测 processID的变化,以及自动支持 undo/redo,我们需要在访问和更改数据对象时告之系统,will/didAccessValueForKey 以及 will/didChangeValueForKey 就是起这个作用的。

3)当我们设置 nil 给数据对象 processID 时,我们可以在 setNilValueForKey 捕获这个情况,并将 processID  置 0;

4)当数据对象被插入到 ManagedObjectContext 时,我们在 awakeFromInsert 将时间设置为当前时间。

三,创建或读取数据对象,设置其值,保存

好,至此真正的万事具备,我们可以创建或从持久化文件中读取数据对象,设置其值,并将其保存到持久化文件中。本例中持久化文件为 xml 文件。修改 main() 中代码如下:

int  main ( int  argc,  const   char   *  argv[])

{

    NSLog( @"  === Core Data Tutorial === " );

     //  Enable GC

     //

    objc_startCollectorThread();

    NSError  * error  =  nil;

    NSManagedObjectModel  * moModel  =  managedObjectModel();

    NSLog( @" The managed object model is defined as follows:\n%@ " , moModel);

     if  (applicationLogDirectory()  ==  nil) {

        exit( 1 );

    }

    NSManagedObjectContext  * moContext  =  managedObjectContext();

     //  Create an Instance of the Run Entity

     //

    NSEntityDescription  * runEntity  =  [[moModel entitiesByName] objectForKey: @" Run " ];

    Run  * run  =  [[Run alloc] initWithEntity:runEntity insertIntoManagedObjectContext:moContext];

    NSProcessInfo  * processInfo  =  [NSProcessInfo processInfo];

    run.processID  =  [processInfo processIdentifier];

     if  ( ! [moContext save:  & error]) {

        NSLog( @" Error while saving\n%@ " , ([error localizedDescription]  !=  nil)  ?  [error localizedDescription] :  @" Unknown Error " );

        exit( 1 );

    }

     //  Fetching Run Objects

     //

    NSFetchRequest  * request  =  [[NSFetchRequest alloc] init];

    [request setEntity:runEntity];

    NSSortDescriptor  * sortDescriptor  =  [[NSSortDescriptor alloc] initWithKey: @" date "  ascending:YES];

    [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];

    error  =  nil;

    NSArray  * array  =  [moContext executeFetchRequest:request error: & error];

     if  ((error  !=  nil)  ||  (array  ==  nil))

    {

        NSLog( @" Error while fetching\n%@ " , ([error localizedDescription]  !=  nil)  ?  [error localizedDescription] :  @" Unknown Error " );

        exit( 1 );

    }

     //  Display the Results

     //

    NSDateFormatter  * formatter  =  [[NSDateFormatter alloc] init];

    [formatter setDateStyle:NSDateFormatterMediumStyle];

    [formatter setTimeStyle:NSDateFormatterMediumStyle];

    NSLog( @" %@ run history: " , [processInfo processName]);

     for  (run  in  array)

    {

        NSLog( @" On %@ as process ID %ld " , [formatter stringForObjectValue:run.date], run.processID);

    }

     return   0 ;

}

在上面的代码中:

1)我们先获得全局的 NSManagedObjectModel 和 NSManagedObjectContext 对象:moModel 和 moContext;

2)并创建一个Run Entity,设置其 Property processID 为当前进程的 ID;

3)将该数据对象保存到持久化文件中:[moContext save: &error]。我们无需与 PersistentStoreCoordinator 打交道,只需要给 ManagedObjectContext 发送 save 消息即可,NSManagedObjectContext 会透明地在后面处理对持久化数据文件的读写;

4)然后我们创建一个 FetchRequest 来查询持久化数据文件中保存的数据记录,并将结果按照日期升序排列。查询操作也是由 ManagedObjectContext 来处理的:[moContextexecuteFetchRequest:request error:&error];

5)将查询结果打印输出;

最后,不要忘记导入相关头文件:

#import  < Foundation / Foundation.h >

#import  < CoreData / CoreData.h >

#include  < objc / objc - auto.h >

#import  " Run.h "

好!大功告成!编译运行,我们可以得到如下显示:

2011-09-03 21:42:47.556 CoreDataTutorial[992:903] CoreDataTutorial run history:

2011-09-03 21:42:47.557 CoreDataTutorial[992:903] On 2011-9-3 下午09:41:56 as process ID 940

2011-09-03 21:42:47.557 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:16 as process ID 955

2011-09-03 21:42:47.558 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:20 as process ID 965

2011-09-03 21:42:47.558 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:24 as process ID 978

2011-09-03 21:42:47.559 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:47 as process ID 992

通过这个例子,我们可以更好理解 Core Data  的运作机制。在 Core Data 中我们最常用的就是 ManagedObjectContext,它几乎参与对数据对象的所有操作,包括对 undo/redo 的支持;而 Entity 对应的运行时类为 ManagedObject,我们可以理解为抽象数据结构 Entity 在内存中由 ManagedObject 来体现,而 Perproty 数据类型在内存中则由 ManagedObject 类的成员属性来体现。一般我们不需要与 PersistentStoreCoordinator 打交道,对数据文件的读写操作都由 ManagedObjectContext 为我们代劳了。

3、使用绑定

前面讲解了  Core Data 的框架,并完全手动编写代码演示了  Core Data 的运作过程。下面我们来演示如何结合 XCode 强大的可视化编辑以及 Cocoa 键值编码,绑定机制来使用 Core Data。有了上面提到的哪些利器,在这个示例中,我们无需编写 NSManagedObjectModel 代码,也无需编写 NSManagedObjectContext,工程模版在背后为我们做了这些事情。

今天要完成的这个示例,有两个 Entity:StudentEntity 与 ClassEntity,各自有一个名为 name 的Attribute 其中 StudentEntity 通过一个名为 inClass 的 relationship 与 ClassEntity关联,而 ClassEntity 也有一个名为 students 的 relationship 与 Entity:StudentEntity 关联,这是一个一对多的关系。此外 ClassEntity 还有一个名为 monitor 的 relationship 关联到 StudentEntity,代表该班的班长。

代码下载: 点击下载

最终的效果图如下:

深入浅出 Cocoa 之 Core Data
深入浅出 Cocoa 之 Core Data

下面我们一步一步来完成这个示例: 1,创建工程: 创建一个 Cocoa Application,工程名为:MacCoreData,并勾选 Create Document-Based Application 和 Use Core Data,在这里要用到 Core Data 和 Document 工程模版,以简化代码的编写。

深入浅出 Cocoa 之 Core Data

2,分类文件: 在 MacCoreData 下新建 Src 和 Res 两个 Group,并将 MyDocument.h 和 MyDocument 拖到 Src 下,将其他 xib 和 xcdatamodeld 拖到 Res 中。将文件分类是个好习惯,尤其是对大项目来说。

深入浅出 Cocoa 之 Core Data

3,创建 Entity: 在工程中,我们可以看到名为 MyDocument.xcdatamodeld 的文件,其后缀表明这是一个 core data model文件,框架就是读取该模型文件生成模型的。下面我们选中这个文件,向其中添加两个实体。点击下方的 Add Entity 增加两个新 Entity: ClassEntity 和 StudentEntity。

深入浅出 Cocoa 之 Core Data
深入浅出 Cocoa 之 Core Data

向 StudentEntity 中添加名为 name 的 string 类型的 Attribute,并设置其 Default Value 为学生甲,去除 Optional 前勾选状态; 向 ClassEntity 中添加名为 name 的 string 类型的 Attribute,并设置其 Default Value 为XX班,去除 Optional 前勾选状态; 选项 Optional 是表示该  Attribute 可选与否的,在这里 name 都是必须的。

向 StudentEntity 中添加名为 inClass 指向 ClassEntity 的 Relationship,其 Inverse 栏要等 ClassEntity 添加了反向关系才能选择,后面回提到; 向 ClassEntity 中添加名为 students 指向 StudentEntity 的 Relationship,其 Inverse 栏选择 inClass,表明这是一个双向关系,勾选 To-Many Relationship,因为一个班级可以有多名学生,这是一对多关系。设定之后,我们可以可以将 StudentEntity 的 inClass 关系的 Inverse 设置为 students了。 再向 ClassEntity 中添加名为 monitor 指向 StudentEntity 的 Relationship,表示该班的班长。

4,生成 NSManagedObject 类: 选中 StudentEntity,然后点击菜单 File-> New -> New file…,添加 Core Data -> NSManagerObject subclass, XCode 就会自动为我们生成 StudentEntity.h 和 StudentEntity.m 文件,记得将这两个文件拖放到 Src Group 下。下面我们来看看这两个文件中有什么内容: StudentEntity.h

#import  < Foundation / Foundation.h >

#import  < CoreData / CoreData.h >

@class ClassEntity;

@interface StudentEntity : NSManagedObject {

@private

}

@property (nonatomic, retain) NSString  *  name;

@property (nonatomic, retain) ClassEntity  *  inClass;

@end

StudentEntity.m

#import  " StudentEntity.h "

#import  " ClassEntity.h "

@implementation StudentEntity

@dynamic name;

@dynamic inClass;

@end

在前面手动代码的示例中,我们是自己编写 Run NSManagedObject的代码,而现在,XCode 已经根据模型文件的描述,自动为我们生成了,方便吧。有时候自动生成的代码不一定满足我们的需要,我们就得对代码进行修改,比如对 ClassEntity 来说,班长只能是其 students 中的一员,如果我们在 students 中移除了班长那个学生,那么该班级的班长就应该置空。

选中 ClassEntity,重复上面的步骤,自动生成 ClassEntity.h 和 ClassEntity.m,下面我们根据需求来修改 ClassEntity.m。 在 - (void)removeStudentsObject:(StudentEntity *)value 的开头添加如下代码:

     if  (value  ==  [self monitor])

        [self setMonitor:nil];

在 - (void)removeStudents:(NSSet *)value 的开头添加如下代码:

     if  ([value containsObject:[self monitor]])

        [self setMonitor:nil];

这样当我们在 students 中删除一个学生时,就会检测该学生是不是班长,如果是,就将该班的班长置空。

5,下面来生成 UI 界面: 在这里,我们是通过切换 view 的方法来显现学生与班级两个界面,因此我们需要主界面,班级以及学生共三个界面。向 MyDocument.xib 中添加如下一个 popup button 和一个 NSBox。并删除 popup 控件中的 menu item,因为我们要通过代码来添加班级,学生项的。

深入浅出 Cocoa 之 Core Data

然后在 Res 中添加两个新 Empty xib 文件:StudentView.xib 和 ClassView.xib,分别向这两个 xib 文件中拖入一个 Custom View,然后在这个 view 添加相关控件构成 UI。记得设置 ClassView 中两个 tableView 的列数为 1,拖入一个 PopupButtonCell 到 StudentView 中班级那一列。效果如下:

深入浅出 Cocoa 之 Core Data
深入浅出 Cocoa 之 Core Data

6,添加 ViewController: 下面我们创建 ViewController 来在程序中转载 xib 文件,显示和切换 view。为了便于切换 view,我们创建一个继承自 NSViewController 的名为:ManagedViewController的类(记得不要创建该类对应的 xib 文件!创建一个 NSObject子类,然后修改其父类为 NSViewController),然后让 StudentViewController 和 ClassViewController 从它继承。

ManagedViewController 类的代码如下: ManagedViewController.h

#import  < Cocoa / Cocoa.h >

@interface ManagedViewController : NSViewController {

@private

    NSManagedObjectContext  *  managedObjectContext;

    NSArrayController  *  contentArrayController;

}

@property (nonatomic, retain) NSManagedObjectContext  *  managedObjectContext;

@property (nonatomic, retain) IBOutlet NSArrayController  * contentArrayController;

@end

ManagedViewController.h #import  " ManagedViewController.h "

@implementation ManagedViewController

@synthesize managedObjectContext;

@synthesize contentArrayController;

-  ( void )dealloc

{

    self.contentArrayController  =  nil;

    self.managedObjectContext  =  nil;

    [super dealloc];

}

//  deal with "Delete" key event.

//

-  ( void ) keyDown:(NSEvent  * )theEvent

{

     if  (contentArrayController) {

         if  ([theEvent keyCode]  ==   51 ) {

            [contentArrayController remove:nil];

        }

         else  {

            [super keyDown:theEvent];

        }

    }

     else  {

        [super keyDown:theEvent];

    }

}

@end

在上面代码中,我们有一个 NSManagedObjectContext * managedObjectContext 指针,它指向 MyDocument 框架中的NSManagedObjectContext对象,后面我们会说到,至于 NSArrayController * contentArrayController,它是一个 IBOutlet,将与xib 中创建的 NSArrayController关联,后面也会说到。在这里引入 contentArrayController 是为了让 delete 能够删除记录。

ClassViewController 类的代码如下:

ClassViewController.h

#import  " ManagedViewController.h "

@interface ClassViewController : ManagedViewController {

@private

}

@end

ClassViewController.m

#import  " ClassViewController.h "

@implementation ClassViewController

-  (id)init

{

    self  =  [super initWithNibName: @" ClassView "  bundle:nil];

     if  (self) {

        [self setTitle: @" 班级 " ];

    }

     return  self;

}

-  ( void )dealloc

{

    [super dealloc];

}

@end

StudentViewController 类的代码如下:

StudentViewController.h

#import  " ManagedViewController.h "

@interface StudentViewController : ManagedViewController {

@private

}

@end

StudentViewController.m #import  " StudentViewController.h "

@implementation StudentViewController

-  (id)init

{

    self  =  [super initWithNibName: @" StudentView "  bundle:nil];

     if  (self) {

        [self setTitle: @" 学生 " ];

    }

     return  self;

}

-  ( void )dealloc

{

    [super dealloc];

}

@end

在这两个子类中,我们在 init 方法中载入 xib 文件,然后设置其 title。

7,创建 NSArrayController,关联对象

现在回到 xib 中来,选中 StudentView.xib,设置StudentView 的 File's Owner 的类为 StudentViewController;使用 Control-Drag 将 File's Owner 的 view 指向 custom view。

向其中拖入两个 NSArrayController:ClassPopup 和 Students。 设置 ClassPopup 的 Object Controller Mode 为 Entity Name,实体名为:ClassEntity,并勾选 Prepare Content。 设置 Students 的 Object Controller Mode 为 Entity Name,实体名为:StudentEntity,并勾选 Prepare Content。

深入浅出 Cocoa 之 Core Data

上面的这些操作,ClassPopup ArrayController 管理 ClassEntity 的数据,Students ArrayController 管理 StudentEntity 的数据,后面我们就要将控件与这些 ArrayController 绑定起来。下面我们将这两个 NSArrayController 的 ManagedObjectContext 参数与 ManagedViewController(File's Owner) 中的 managedObjectContext 绑定起来,这样 NSDocuments 的 NSManagedObjectContext 就作用到的 ArrayController 中来了。下面只演示了 ClassPopup,请自行完成 Students 的绑定:

深入浅出 Cocoa 之 Core Data

前面我们在 ManagedViewController 创建了一个 IBOutlet contentArrayController,现在是将它关联的时候了,使用 Control-Drag 将 File's Owner 的 contentArrayController 关联到 Students。

重复上面的过程,选中 ClassView.xib,将 File's Owner 的类为 ClassViewController,并将其 view 指向 custom view。

向其中拖入三个 NSArrayController:Classes,MonitorPopup 和 Students。 设置 Classes 的 Object Controller Mode 为 Entity Name,实体名为:ClassEntity,并勾选 Prepare Content。 将 Classes 的 ManagedObjectContext 参数与 ManagedViewController(File's Owner) 中的 managedObjectContext 绑定起来。

注意:这里没有对 MonitorPopup 和 Students 进行修改。

使用 Control-Drag 将 File's Owner 的 contentArrayController 关联到 Classes。 将 Students 和 MonitorPopup 的 Content set 绑定到 Classes 的  Model key path: students,表示这两个 ArrayController  是管理对应 ClassEntity 的 students 的数据。