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

一次來了解一下 PersistentStore、DataModel、PersistentStoreCoordinator、ManagedObjects、ManagedObjectsContext、FetchRequest 這些概念。
PersistentStore
這個是數(shù)據(jù)真正存儲的地方,CodeData提供了兩種存儲的選擇,分別是sqlite和二進(jìn)制文件。PersistentStore本身并不是objc類,僅僅是數(shù)據(jù)存儲。
DataModel
對應(yīng)的objc類為 NSManagedObjectModel,一個典型的應(yīng)用如:
- /**
- 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文件中分開管理,然后用這個函數(shù)連接起來生成一個datamodel,這樣就可以對應(yīng)一個persistentStore。
PersistentStoreCoordinator
對應(yīng)的objc類為NSPersistentStoreCoordinator,這個類用來控制對PersistentStore的訪問。PersistentStoreCoordinator提供了一些列的高級調(diào)用供其他類來使用,對PersistentStore進(jìn)行讀和寫。下面看一段典型的代碼:
- /**
- 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;
- }
這里默認(rèn)存儲形式為sqlite,并且存儲文件為CoreData.sqlite,這段代碼比較簡單,創(chuàng)建了persistentStoreCoordinator實例。
ManagedObjects
對應(yīng)的類為NSManagedObject。上面的CoreData框架圖中有Entities,Entity定義了數(shù)據(jù)的結(jié)構(gòu),但他并不是數(shù)據(jù),真正的數(shù)據(jù)實例是NSManagedObject類或他的子類。
NSManagedObject類支持Key-Value 編碼(KVC),像NSDictionary差不多。NSManagedObject提供了valueForKey:和setValue:forKey:用來設(shè)置和查詢的方法。另外他也提供了對關(guān)系操作的方法。
下面是幾個典型的代碼案例:
- NSDate *timeStamp = [managedObject valueForKey:@"timeStamp"];
- [managedObject setValue:[NSDate date] forKey:@"timeStamp"];
另外KVC也支持keypath,如有兩個數(shù)據(jù)entity,一個是Employee,一個事Employer,Employee中有個屬性石whereIWork,而這個屬性用relationship連接到了對應(yīng)的Employer,Employer中有個屬性石name,這樣要查詢一個Employer的name,可以用keypath的形式,whereIWork.name。
- NSString *employerName = [managedObject valueForKeyPath:@"whereIWork.name"];
ManagedObjectsContext
對應(yīng)的類為NSManagedObjectsContext。 這個類是一個用戶對persistentStore操作的網(wǎng)關(guān),他維護(hù)了用戶創(chuàng)建或者加載的managed objects。他記錄了用戶對managed objects的所有改變,以便用來undo或redo,另外當(dāng)用戶要存儲現(xiàn)在的managed objects到persistentstore時,只需調(diào)用managedObjectsContext的save方法就行了。
每個應(yīng)用至少需要一個context,當(dāng)然可以同時存在多個context,比如多線程時,如NSOperationQueue。context并不是線程安全的,因此在這種情況中用戶要自己做好安全工作。
下面是一個簡單應(yīng)用實例。
- /**
- 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,其實用戶打交道最多的就是這個控制器了。要講的東西很多,放到下面一篇吧。
這篇文章重點講講CoreData的Fetched Results Controller。
對應(yīng)的objc類為NSFetchedResultsController。這個類是用來管理CoreData Fetch request返回的對象的。
在創(chuàng)建這個控制器之前,必須先創(chuàng)建fetch request。 fetch request描述了詳細(xì)的查詢規(guī)則,還可以添加查詢結(jié)果的排序描述(sort descriptor)。fetchResultsController根據(jù)已經(jīng)創(chuàng)建完的fetch request來創(chuàng)建, 它是NSFetchedResultsController的實例,這個實例的主要任務(wù)就是使用fetch request來保證它所關(guān)聯(lián)的數(shù)據(jù)的新鮮性。創(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;
- [aFetchedResultsController release];
- [fetchRequest release];
- [sortDescriptor release];
- [sortDescriptors release];
- return fetchedResultsController;
- }
`這個函數(shù)用來創(chuàng)建FetchedResultsController,過程還是比較簡單的,下面是初始化這個控制器代碼。
- NSError *error = nil;
- if(![[self fetchedResultsController]performFetch: &error]){
- //handle the error appropriately
- NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
- exit(-1);
- }
這段代碼一般會放在viewDidLoad函數(shù)中,初始化之后,fetchedResultsController就與數(shù)據(jù)相連接了,之后要取數(shù)據(jù)都能直接從這個控制器提供的方法中去取。
實現(xiàn)這個控制器,最關(guān)鍵的還要實現(xiàn)Fetched Results Controller Delegate Methods??刂破髋c數(shù)據(jù)源連接后,控制器監(jiān)視器會時刻監(jiān)視著數(shù)據(jù)源,當(dāng)數(shù)據(jù)源發(fā)生
改變后,監(jiān)視器會調(diào)用對應(yīng)的協(xié)議方法,改協(xié)議總共要實現(xiàn)四個方法,分別為:
- - (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
當(dāng)控制器監(jiān)控的數(shù)據(jù)發(fā)生改變時,如對象被刪除,有插入,更新等,監(jiān)視器會在數(shù)據(jù)發(fā)生改變前意識到這個情況,此時就會調(diào)用這個函數(shù)。往往我們用列表的形式
表現(xiàn)數(shù)據(jù),此時意味著屏幕上的數(shù)據(jù)即將過時,因為數(shù)據(jù)馬上要改變了,這是這個協(xié)議方法的工作就是通知列表數(shù)據(jù)馬上要更新的消息,往往代碼是這樣實現(xiàn)的。
- - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
- [self.tableView beginUpdates];
- }
2. - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
當(dāng)fetchedResultsController完成對數(shù)據(jù)的改變時,監(jiān)視器會調(diào)用這個協(xié)議方法。在上面提到的情況,這個方法要通知列表數(shù)據(jù)已經(jīng)完成,可以更新顯示的數(shù)據(jù)這個
消息,因此通常的實現(xiàn)是這樣的。
- - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
- [self.tableView endUpdates];
- }
3. - (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
當(dāng)fetchedResultsController發(fā)現(xiàn)指定的對象有改變時,監(jiān)視器會調(diào)用這個協(xié)議方法。這里改變的類型從列表中體現(xiàn)有 更新、插入、刪除或者行的移動。因此這個
方法要實現(xiàn)所有的這些方法,以應(yīng)對任何一種改變。下面是這個方法的標(biāo)準(zhǔn)實現(xiàn)。
- - (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;
- }
- }
從上面的代碼可以看出,插入,刪除,移動是比較簡單的,最復(fù)雜的是更新。這個代碼是xcode的模板代碼,基本能適用我們遇到的情況,對更新里面的代碼我還不是非常確定,所以這里留著等過幾天完全吃透了再補(bǔ)上。
4. - (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type
當(dāng)改變控制器管理的對象后引起了列表section的變化,此時監(jiān)視器就會調(diào)用這個協(xié)議函數(shù)。
下面是標(biāo)準(zhǔn)實現(xiàn)。
- - (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;
- }
- }
|