天天看點

深入淺出 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 的資料。

繼續閱讀