基本結構
CoreData相當于一個綜合的數據存儲和管理中心,它支持sqlite,二進制存儲文件兩種形式的數據存儲。而CoreData提供了存儲管理,包括查詢、插入、刪除、更新、回滾、會話管理、鎖管理等一系列數據庫操作。另外,開發(fā)者還可以在Xcode中使用 .xcdatamodel 擴展名的文件,以圖形化的形式編輯數據模型,這里包括Entities、Properties、Attributes、Relationships四個概念,這里跟關系型數據庫有很大的相似點。
下面來看一下CoreData的框架圖。

常用類型
一次了解一下 **PersistentStore、DataModel、PersistentStoreCoordinator、ManagedObjects、ManagedObjectsContext、FetchRequest **這些概念。
- 1.PersistentStore
這個是數據真正存儲的地方,CodeData提供了兩種存儲的選擇,分別是sqlite和二進制文件。PersistentStore本身并不是objc類,僅僅是數據存儲。 - 2.DataModel
對應的objc類為 NSManagedObjectModel,一個典型的應用如:
//Returns the managed object model for the application.
If the model doesn't already exist, it is created by merging all of the models
found in the application bundle.
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
return managedObjectModel;
}
// 這里用了iPhone開發(fā)中典型的laze loading,
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
// 參數中的nil表示連接項目中所有的 .xcodemodel 文件為一個datamodel,這是一個非常好的方法,
// 把多個entity放在各自的xcodemodel文件中分開管理,然后用這個函數連接起來生成一個datamodel,這樣就可以對應一個persistentStore。
- 3.PersistentStoreCoordinator
對應的objc類為NSPersistentStoreCoordinator,這個類用來控制對PersistentStore的訪問。PersistentStoreCoordinator提供了一些列的高級調用供其他類來使用,對PersistentStore進行讀和寫,比如增查刪改。下面看一段典型的代碼:
//Returns the persistent store coordinator for the application.
If the coordinator doesn't already exist, it is created and the application's store
added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
stringByAppendingPathComponent: @"CoreData.sqlite"]];
NSError *error;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel: [self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeUrl options:nil error:&error]) {
// Handle error
}
return persistentStoreCoordinator;
}
這里默認存儲形式為sqlite,并且存儲文件為CoreData.sqlite,這段代碼比較簡單,創(chuàng)建了persistentStoreCoordinator實例。
- 4.ManagedObjects
對應的objc類為NSManagedObject。上面的CoreData框架圖中有Entities,Entity定義了數據的結構,但他并不是數據,真正的數據實例是NSManagedObject類或他的子類。
NSManagedObject類支持Key-Value 編碼(KVC),像NSDictionary差不多。NSManagedObject提供了valueForKey:和setValue:forKey:用來設置和查詢的方法。另外他也提供了對關系操作的方法。
下面是幾個典型的代碼案例:
NSDate *timeStamp = [managedObject valueForKey:@"timeStamp"];
[managedObject setValue:[NSDate date] forKey:@"timeStamp"];
另外KVC也支持keypath,如有兩個數據entity,一個是Employee,一個事Employer,Employee中有個屬性石whereIWork,而這個屬性用relationship連接到了對應的Employer,Employer中有個屬性石name,這樣要查詢一個Employer的name,可以用keypath的形式,whereIWork.name。
NSString *employerName = [managedObject valueForKeyPath:@"whereIWork.name"];
- 5.ManagedObjectsContext
對應的objc類為NSManagedObjectsContext。 這個類是一個用戶對persistentStore操作的網關,他維護了用戶創(chuàng)建或者加載的managed objects。他記錄了用戶對managed objects的所有改變,以便用來undo或redo,另外當用戶要存儲現在的managed objects到persistentstore時,只需調用managedObjectsContext的save方法就行了。
每個應用至少需要一個context,當然可以同時存在多個context,比如多線程時,如NSOperationQueue。context并不是線程安全的,因此在這種情況中用戶要自己做好安全工作。
下面是一個簡單應用實例。
/**
Returns the managed object context for the application.
If the context doesn't already exist, it is created and bound to the persistent
store coordinator for the application.
*/
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}
這個代碼也比較簡單,不做解釋了。
FetchRequest(FetchRequestController)
這里重點講FetchRequestController,其實用戶打交道最多的就是這個控制器了。 對應的objc類為NSFetchedResultsController。這個類是用來管理CoreData Fetch request返回的對象的。
在創(chuàng)建這個控制器之前,必須先創(chuàng)建fetch request。 fetch request描述了詳細的查詢規(guī)則,還可以添加查詢結果的排序描述(sort descriptor)。fetchResultsController根據已經創(chuàng)建完的fetch request來創(chuàng)建, 它是NSFetchedResultsController的實例,這個實例的主要任務就是使用fetch request來保證它所關聯的數據的新鮮性。創(chuàng)建了fetchResultsController實例后要做一下初始化,一般初始化是向這個控制器發(fā)送PerformFetch消息,下面是這一過程的代碼。
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event"
inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:@"timeStamp" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor,nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName:@"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
return fetchedResultsController;
}
這個函數用來創(chuàng)建FetchedResultsController,過程還是比較簡單的,下面是初始化這個控制器代碼。
NSError *error = nil;
if(![[self fetchedResultsController]performFetch: &error]){
//handle the error appropriately
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
exit(-1);
}
這段代碼一般會放在viewDidLoad函數中,初始化之后,fetchedResultsController就與數據相連接了,之后要取數據都能直接從這個控制器提供的方法中去取。
實現這個控制器,最關鍵的還要實現Fetched Results Controller Delegate Methods??刂破髋c數據源連接后,控制器監(jiān)視器會時刻監(jiān)視著數據源,當數據源發(fā)生
改變后,監(jiān)視器會調用對應的協(xié)議方法,改協(xié)議總共要實現四個方法,分別為:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath;
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type;
下面依次來解釋這四個協(xié)議方法。
*1. - (void)controllerWillChangeContent:(NSFetchedResultsController )controller
當控制器監(jiān)控的數據發(fā)生改變時,如對象被刪除,有插入,更新等,監(jiān)視器會在數據發(fā)生改變前意識到這個情況,此時就會調用這個函數。往往我們用列表的形式表現數據,此時意味著屏幕上的數據即將過時,因為數據馬上要改變了,這是這個協(xié)議方法的工作就是通知列表數據馬上要更新的消息,往往代碼是這樣實現的。
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
*2. - (void)controllerDidChangeContent:(NSFetchedResultsController )controller
當fetchedResultsController完成對數據的改變時,監(jiān)視器會調用這個協(xié)議方法。在上面提到的情況,這個方法要通知列表數據已經完成,可以更新顯示的數據這個消息,因此通常的實現是這樣的。
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
** 3. - (void)controller:(NSFetchedResultsController )controller*
** didChangeObject:(id)anObject **
** atIndexPath:(NSIndexPath )indexPath **
** forChangeType:(NSFetchedResultsChangeType)type **
** newIndexPath:(NSIndexPath )newIndexPath
當fetchedResultsController發(fā)現指定的對象有改變時,監(jiān)視器會調用這個協(xié)議方法。這里改變的類型從列表中體現有 更新、插入、刪除或者行的移動。因此這個方法要實現所有的這些方法,以應對任何一種改變。下面是這個方法的標準實現。
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate: {
NSString *sectionKeyPath = [controller sectionNameKeyPath];
if (sectionKeyPath == nil)
break;
NSManagedObject *changedObject = [controller objectAtIndexPath:indexPath];
NSArray *keyParts = [sectionKeyPath componentsSeparatedByString:@"."];
id currentKeyValue = [changedObject valueForKeyPath:sectionKeyPath];
for (int i = 0; i < [keyParts count] - 1; i++) {
NSString *onePart = [keyParts objectAtIndex:i];
changedObject = [changedObject valueForKey:onePart];
}
sectionKeyPath = [keyParts lastObject];
NSDictionary *committedValues = [changedObject committedValuesForKeys:nil];
if ([[committedValues valueForKeyPath:sectionKeyPath]isEqual:currentKeyValue])
break;
NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (tableSectionCount != frcSectionCount) {
// Need to insert a section
NSArray *sections = controller.sections;
NSInteger newSectionLocation = -1;
for (id oneSection in sections) {
NSString *sectionName = [oneSection name];
if ([currentKeyValue isEqual:sectionName]) {
newSectionLocation = [sections indexOfObject:oneSection];
break;
}
}
if (newSectionLocation == -1)
return; // uh oh
if (!((newSectionLocation == 0) && (tableSectionCount == 1)
&& ([self.tableView numberOfRowsInSection:0] == 0)))
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:newSectionLocation]
withRowAnimation:UITableViewRowAnimationFade];
NSUInteger indices[2] = {newSectionLocation, 0};
newIndexPath = [[[NSIndexPath alloc] initWithIndexes:indiceslength:2] autorelease];
}
}
case NSFetchedResultsChangeMove
if (newIndexPath != nil) {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath]
withRowAnimation: UITableViewRowAnimationRight];
}
else {
[self.tableView reloadSections:[NSIndexSet
indexSetWithIndex:[indexPath section]]withRowAnimation:UITableViewRowAnimationFade];
}
break;
default:
break;
}
}
從上面的代碼可以看出,插入,刪除,移動是比較簡單的,最復雜的是更新。這個代碼是xcode的模板代碼,基本能適用我們遇到的情況.
** 4. - (void)controller:(NSFetchedResultsController )controller*
** didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo**
** atIndex:(NSUInteger)sectionIndex **
** forChangeType:(NSFetchedResultsChangeType)type**
當改變控制器管理的對象后引起了列表section的變化,此時監(jiān)視器就會調用這個協(xié)議函數。
下面是標準實現。
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1)
&& ([self.tableView numberOfRowsInSection:0] == 0)))
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1)
&& ([self.tableView numberOfRowsInSection:0] == 0)))
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
case NSFetchedResultsChangeUpdate:
default:
break;
}
}
參考鏈接
CoreData之FetchRequestController
CoreData入門
深入淺出 Cocoa 之 Core Data
XMPP介紹二:Core Data