CoreData之FetchRequestController

我目前的理解,CoreData相当于一个综合的数据存储和管理中心,它支持sqlite,二进制存储文件两种形式的数据存储。而CoreData提供了存储管理,包括查询、插入、

删除、更新、回滚、会话管理、锁管理等一系列数据库操作。另外,开发者还可以在xcode中使用 .xcdatamodel 扩展名的文件,以图形化的形式编辑数据模型,这里包括了

Entities、Properties、Attributes、Relationships四个概念,这里跟关系型数据库有很大的相似点。

                  下面来看一下CoreData的框架。



                一次来了解一下 PersistentStore、DataModel、PersistentStoreCoordinator、ManagedObjects、ManagedObjectsContext、FetchRequest 这些概念。

                PersistentStore

                            这个是数据真正存储的地方,CodeData提供了两种存储的选择,分别是sqlite和二进制文件。PersistentStore本身并不是objc类,仅仅是数据存储。

               DataModel

                              对应的objc类为 NSManagedObjectModel,一个典型的应用如:

 

[cpp]  view plain copy
  1. /** 
  2. Returns the managed object model for the application. 
  3. If the model doesn't already exist, it is created by merging all of the models 
  4. found in the application bundle. 
  5. */  
  6. - (NSManagedObjectModel *)managedObjectModel {  
  7. if (managedObjectModel != nil) {  
  8. return managedObjectModel;  
  9. }  
  10. managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];  
  11. return managedObjectModel;  
  12. }  
这里用了iPhone开发中典型的laze loading,而
[cpp]  view plain copy
  1. managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];  

中的nil表示连接项目中所有的 .xcodemodel 文件为一个datamodel,这是一个非常好的方法,把多个entity放在各自的xcodemodel文件中分开管理,然后用这个函数连接起来生成一个datamodel,这样就可以对应一个persistentStore。

PersistentStoreCoordinator

     对应的objc类为NSPersistentStoreCoordinator,这个类用来控制对PersistentStore的访问。PersistentStoreCoordinator提供了一些列的高级调用供其他类来使用,对PersistentStore进行读和写。下面看一段典型的代码:

 

[cpp]  view plain copy
  1. /** 
  2. Returns the persistent store coordinator for the application. 
  3. If the coordinator doesn't already exist, it is created and the application's store 
  4. added to it. 
  5. */  
  6. - (NSPersistentStoreCoordinator *)persistentStoreCoordinator {  
  7.     if (persistentStoreCoordinator != nil) {  
  8.         return persistentStoreCoordinator;  
  9.     }  
  10.     NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]  
  11.     stringByAppendingPathComponent: @"CoreData.sqlite"]];  
  12.     NSError *error;  
  13.     persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]  
  14.     initWithManagedObjectModel: [self managedObjectModel]];  
  15.     if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType  
  16.     configuration:nil URL:storeUrl options:nil error:&error]) {  
  17.     // Handle error  
  18.     }  
  19.     return persistentStoreCoordinator;  
  20. }  
这里默认存储形式为sqlite,并且存储文件为CoreData.sqlite,这段代码比较简单,创建了persistentStoreCoordinator实例。

 

ManagedObjects

    对应的类为NSManagedObject。上面的CoreData框架图中有Entities,Entity定义了数据的结构,但他并不是数据,真正的数据实例是NSManagedObject类或他的子类。

    NSManagedObject类支持Key-Value 编码(KVC),像NSDictionary差不多。NSManagedObject提供了valueForKey:和setValue:forKey:用来设置和查询的方法。另外他也提供了对关系操作的方法。

   下面是几个典型的代码案例:

 

[cpp]  view plain copy
  1. NSDate *timeStamp = [managedObject valueForKey:@"timeStamp"];  

[cpp]  view plain copy
  1. [managedObject setValue:[NSDate date] forKey:@"timeStamp"];  

另外KVC也支持keypath,如有两个数据entity,一个是Employee,一个事Employer,Employee中有个属性石whereIWork,而这个属性用relationship连接到了对应的Employer,Employer中有个属性石name,这样要查询一个Employer的name,可以用keypath的形式, whereIWork.name

 

 

[cpp]  view plain copy
  1. NSString *employerName = [managedObject valueForKeyPath:@"whereIWork.name"];  

 

ManagedObjectsContext

    对应的类为NSManagedObjectsContext。 这个类是一个用户对persistentStore操作的网关,他维护了用户创建或者加载的managed objects。他记录了用户对managed objects的所有改变,以便用来undo或redo,另外当用户要存储现在的managed objects到persistentstore时,只需调用managedObjectsContext的save方法就行了。

    每个应用至少需要一个context,当然可以同时存在多个context,比如多线程时,如NSOperationQueue。context并不是线程安全的,因此在这种情况中用户要自己做好安全工作。

    下面是一个简单应用实例。

[cpp]  view plain copy
  1. /** 
  2. Returns the managed object context for the application. 
  3. If the context doesn't already exist, it is created and bound to the persistent 
  4. store coordinator for the application. 
  5. */  
  6. - (NSManagedObjectContext *) managedObjectContext {  
  7.     if (managedObjectContext != nil) {  
  8.         return managedObjectContext;  
  9.     }  
  10.     NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];  
  11.     if (coordinator != nil) {  
  12.         managedObjectContext = [[NSManagedObjectContext alloc] init];  
  13.         [managedObjectContext setPersistentStoreCoordinator: coordinator];  
  14.     }  
  15.     return managedObjectContext;  
  16. }  

这个代码也比较简单,不做解释了。

 

FetchRequest(FetchRequestController)

 

这里重点讲FetchRequestController,其实用户打交道最多的就是这个控制器了。要讲的东西很多,放到下面一篇吧。

这篇文章重点讲讲CoreData的Fetched Results Controller。

             对应的objc类为NSFetchedResultsController。这个类是用来管理CoreData Fetch request返回的对象的。

             在创建这个控制器之前,必须先创建fetch request。 fetch request描述了详细的查询规则,还可以添加查询结果的排序描述(sort descriptor)。fetchResultsController根据已经创建完的fetch request来创建, 它是NSFetchedResultsController的实例,这个实例的主要任务就是使用fetch request来保证它所关联的数据的新鲜性。创建了fetchResultsController实例后要做一下初始化,一般初始化是向这个控制器发送PerformFetch消息,下面是这一过程的代码。

[cpp]  view plain copy
  1. - (NSFetchedResultsController *)fetchedResultsController {    
  2.     if (fetchedResultsController != nil) {    
  3.         return fetchedResultsController;    
  4.     }    
  5.     /*  
  6.     Set up the fetched results controller.  
  7.     */    
  8.     // Create the fetch request for the entity.    
  9.     NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];    
  10.     // Edit the entity name as appropriate.    
  11.     NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event"    
  12.     inManagedObjectContext:managedObjectContext];    
  13.     [fetchRequest setEntity:entity];    
  14.     // Set the batch size to a suitable number.    
  15.     [fetchRequest setFetchBatchSize:20];    
  16.     // Edit the sort key as appropriate.    
  17.     NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]    
  18.     initWithKey:@"timeStamp" ascending:NO];    
  19.     NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor,nil];    
  20.     [fetchRequest setSortDescriptors:sortDescriptors];    
  21.     // Edit the section name key path and cache name if appropriate.    
  22.     // nil for section name key path means "no sections".    
  23.     NSFetchedResultsController *aFetchedResultsController =    
  24.     [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest    
  25.     managedObjectContext:managedObjectContext sectionNameKeyPath:nil    
  26.     cacheName:@"Root"];    
  27.     aFetchedResultsController.delegate = self;    
  28.     self.fetchedResultsController = aFetchedResultsController;    
  29.     [aFetchedResultsController release];    
  30.     [fetchRequest release];    
  31.     [sortDescriptor release];    
  32.     [sortDescriptors release];    
  33.     return fetchedResultsController;    
  34. }    

            `这个函数用来创建FetchedResultsController,过程还是比较简单的,下面是初始化这个控制器代码。

[cpp]  view plain copy
  1. NSError *error = nil;    
  2. if(![[self  fetchedResultsController]performFetch: &error]){    
  3.     //handle the error appropriately    
  4.     NSLog(@"Unresolved error %@, %@", error, [error userInfo]);    
  5.     exit(-1);    
  6. }    

          这段代码一般会放在viewDidLoad函数中,初始化之后,fetchedResultsController就与数据相连接了,之后要取数据都能直接从这个控制器提供的方法中去取。

            实现这个控制器,最关键的还要实现Fetched Results Controller Delegate Methods。控制器与数据源连接后,控制器监视器会时刻监视着数据源,当数据源发生

改变后,监视器会调用对应的协议方法,改协议总共要实现四个方法,分别为:

[cpp]  view plain copy
  1. - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;    
  2. - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;    
  3. - (void)controller:(NSFetchedResultsController *)controller    
  4.    didChangeObject:(id)anObject    
  5.        atIndexPath:(NSIndexPath *)indexPath    
  6.      forChangeType:(NSFetchedResultsChangeType)type    
  7.       newIndexPath:(NSIndexPath *)newIndexPath;    
  8. - (void)controller:(NSFetchedResultsController *)controller    
  9.   didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo    
  10.            atIndex:(NSUInteger)sectionIndex    
  11.      forChangeType:(NSFetchedResultsChangeType)type;    

 

              下面依次来解释这四个协议方法。

              1.  - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller

            当控制器监控的数据发生改变时,如对象被删除,有插入,更新等,监视器会在数据发生改变前意识到这个情况,此时就会调用这个函数。往往我们用列表的形式

表现数据,此时意味着屏幕上的数据即将过时,因为数据马上要改变了,这是这个协议方法的工作就是通知列表数据马上要更新的消息,往往代码是这样实现的。 
[cpp]  view plain copy
  1. - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {    
  2.     [self.tableView beginUpdates];    
  3. }   

 

           2. - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller

            当fetchedResultsController完成对数据的改变时,监视器会调用这个协议方法。在上面提到的情况,这个方法要通知列表数据已经完成,可以更新显示的数据这个

消息,因此通常的实现是这样的。

 

[cpp]  view plain copy
  1. - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {    
  2.     [self.tableView endUpdates];    
  3. }    
  3. - (void)controller:(NSFetchedResultsController *)controller

 

 

              didChangeObject:(id)anObject 

                        atIndexPath:(NSIndexPath *)indexPath 

                  forChangeType:(NSFetchedResultsChangeType)type 

                    newIndexPath:(NSIndexPath *)newIndexPath

               当fetchedResultsController发现指定的对象有改变时,监视器会调用这个协议方法。这里改变的类型从列表中体现有 更新、插入、删除或者行的移动。因此这个

方法要实现所有的这些方法,以应对任何一种改变。下面是这个方法的标准实现。

[cpp]  view plain copy
  1. - (void)controller:(NSFetchedResultsController *)controller    
  2.    didChangeObject:(id)anObject    
  3.        atIndexPath:(NSIndexPath *)indexPath    
  4.      forChangeType:(NSFetchedResultsChangeType)type    
  5.       newIndexPath:(NSIndexPath *)newIndexPath {    
  6.     switch(type) {    
  7.         case NSFetchedResultsChangeInsert:    
  8.             [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]     
  9.                             withRowAnimation:UITableViewRowAnimationFade];    
  10.             break;    
  11.         case NSFetchedResultsChangeDelete:    
  12.             [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]    
  13.                             withRowAnimation:UITableViewRowAnimationFade];    
  14.             break;    
  15.         case NSFetchedResultsChangeUpdate: {    
  16.             NSString *sectionKeyPath = [controller sectionNameKeyPath];    
  17.             if (sectionKeyPath == nil)    
  18.                 break;    
  19.             NSManagedObject *changedObject = [controller objectAtIndexPath:indexPath];    
  20.             NSArray *keyParts = [sectionKeyPath componentsSeparatedByString:@"."];    
  21.             id currentKeyValue = [changedObject valueForKeyPath:sectionKeyPath];    
  22.             for (int i = 0; i < [keyParts count] - 1; i++) {    
  23.                 NSString *onePart = [keyParts objectAtIndex:i];    
  24.                 changedObject = [changedObject valueForKey:onePart];    
  25.             }    
  26.             sectionKeyPath = [keyParts lastObject];    
  27.             NSDictionary *committedValues = [changedObject committedValuesForKeys:nil];    
  28.             if ([[committedValues valueForKeyPath:sectionKeyPath]isEqual:currentKeyValue])    
  29.                 break;    
  30.             NSUInteger tableSectionCount = [self.tableView numberOfSections];    
  31.             NSUInteger frcSectionCount = [[controller sections] count];    
  32.             if (tableSectionCount != frcSectionCount) {    
  33.                 // Need to insert a section    
  34.                 NSArray *sections = controller.sections;    
  35.                 NSInteger newSectionLocation = -1;    
  36.                 for (id oneSection in sections) {    
  37.                     NSString *sectionName = [oneSection name];    
  38.                     if ([currentKeyValue isEqual:sectionName]) {    
  39.                         newSectionLocation = [sections indexOfObject:oneSection];    
  40.                         break;    
  41.                     }    
  42.                 }    
  43.                 if (newSectionLocation == -1)    
  44.                     return// uh oh    
  45.                 if (!((newSectionLocation == 0) && (tableSectionCount == 1)    
  46.                        && ([self.tableView numberOfRowsInSection:0] == 0)))    
  47.                     [self.tableView insertSections:[NSIndexSet indexSetWithIndex:newSectionLocation]    
  48.                                   withRowAnimation:UITableViewRowAnimationFade];    
  49.                 NSUInteger indices[2] = {newSectionLocation, 0};    
  50.                 newIndexPath = [[[NSIndexPath alloc] initWithIndexes:indiceslength:2] autorelease];    
  51.             }    
  52.         }    
  53.         case NSFetchedResultsChangeMove    
  54.             if (newIndexPath != nil) {    
  55.                 [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]    
  56.                                       withRowAnimation:UITableViewRowAnimationFade];    
  57.                 [self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath]    
  58.                                       withRowAnimation: UITableViewRowAnimationRight];    
  59.             }    
  60.             else {    
  61.                 [self.tableView reloadSections:[NSIndexSet    
  62.                 indexSetWithIndex:[indexPath section]]withRowAnimation:UITableViewRowAnimationFade];    
  63.             }    
  64.             break;    
  65.         default:    
  66.             break;    
  67.     }    
  68. }    

 

 

          从上面的代码可以看出,插入,删除,移动是比较简单的,最复杂的是更新。这个代码是xcode的模板代码,基本能适用我们遇到的情况,对更新里面的代码我还不是非常确定,所以这里留着等过几天完全吃透了再补上。

 

 

           4. - (void)controller:(NSFetchedResultsController *)controller

            didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo

                                atIndex:(NSUInteger)sectionIndex 

                 forChangeType:(NSFetchedResultsChangeType)type

              当改变控制器管理的对象后引起了列表section的变化,此时监视器就会调用这个协议函数。

            下面是标准实现。 

[cpp]  view plain copy
  1. - (void)controller:(NSFetchedResultsController *)controller    
  2.   didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo    
  3.            atIndex:(NSUInteger)sectionIndex    
  4.      forChangeType:(NSFetchedResultsChangeType)type {    
  5.     switch(type) {    
  6.         case NSFetchedResultsChangeInsert:    
  7.             if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1)    
  8.                              && ([self.tableView numberOfRowsInSection:0] == 0)))    
  9.                 [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]    
  10.                               withRowAnimation:UITableViewRowAnimationFade];    
  11.             break;    
  12.         case NSFetchedResultsChangeDelete:    
  13.             if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1)    
  14.                              && ([self.tableView numberOfRowsInSection:0] == 0)))    
  15.                 [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]    
  16.                               withRowAnimation:UITableViewRowAnimationFade];    
  17.             break;    
  18.         case NSFetchedResultsChangeMove:    
  19.         case NSFetchedResultsChangeUpdate:    
  20.         default:    
  21.             break;    
  22.     }    
  23. }    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值