深入淺出 Cocoa 之 Core Data(2)- 程式碼範例
羅朝輝(http://blog.csdn.net/kesalin)
CC 許可,轉載請註明出處
前面詳細講解了 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資訊等的便於理解的描述,其可用的索引值對如下表:
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
#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 kesalin@gmail.com. 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 來處理的:[moContext
executeFetchRequest:request error:&error];
5)將查詢結果列印輸出;
大功告成!編譯運行,我們可以得到如下顯示:
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 9402011-09-03 21:42:47.557 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:16 as process ID 9552011-09-03 21:42:47.558 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:20 as process ID 9652011-09-03 21:42:47.558 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:24 as process ID 9782011-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 為我們代勞了。