http://blog.csdn.net/feosun/article/details/8226534
基本概念
在CoreData有一些概念刚学习的时候不是很容易理解,还是要单独拿出来来梳理一下,这样学后面的内容不会感觉吃力。
♥ 表结构:NSEntityDescription
♥ 表记录:NSManagedObject
--------------------------------------------------------------------------------
♥ 数据库存放方式:NSPersistentStoreCoordinator(持久化存储协调者)
♥ 数据库操作:NSManagedObjectContext(被管理的对象上下文)
还有一些类NSManagedObjectModel、NSFetchRequest什么的,具体项目就会有体会,下面来实战一下。
新建一个项目,项目模板基于“Master-Detail Application”,点击“Next”按钮,项目命名为“SimpleCoreData”,并勾选“Use Core Data”,点击“Next”,选择项目保存的目录,点击“Create”按钮,项目创建完毕。
代码分析
比以前创建的简单项目多了不少代码,还有xcdatamodeld文件,慢慢分析代码,AppDelegate.h头文件中,添加了三个property
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
再分析AppDelegate.m文件,有对应的三个方法来返回各自对应的对象
#pragma mark - Core Data stack
- (NSManagedObjectContext *)managedObjectContext
{
...
return __managedObjectContext;
}
- (NSManagedObjectModel *)managedObjectModel
{
...
return __managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
...
return __persistentStoreCoordinator;
}
这些对象在哪里被调用的呢,打开MasterViewController.m,在初始化函数中,我们看到通过获取delegate,再通过delegate调用方法managedObjectContext,这样就得到了这个NSManagedObjectContext对象,NSManagedObjectContext对象它会跟NSPersistentStoreCoordinator对象打交道,NSPersistentStoreCoordinator会去处理底层的存储方式。
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = NSLocalizedString(@"Master", @"Master");
id delegate = [[UIApplication sharedApplication] delegate];
self.managedObjectContext = [delegate managedObjectContext];
}
return self;
}查询实体
分析MasterViewController.m的代码发现以下函数的调用顺序。
♥ -tableView:(UITableView *)tableView cellForRowAtIndexPath:
♥ -configureCell:atIndexPath:
♥ -fetchedResultsController
最后是从fetchedResultsController获取到查询结果,那就有必要来分析一下
- (NSFetchedResultsController *)fetchedResultsController
{
// 如果查询结果已经存在就直接返回__fetchedResultsController
if (__fetchedResultsController != nil)
{
return __fetchedResultsController;
}
// 1.创建NSFetchRequest对象(相当于SQL语句)
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// 2.创建查询实体(相当于设置查询哪个表)
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// 设置获取数据的批数.
[fetchRequest setFetchBatchSize:20];
// 3.创建排序描述符,(ascending:是否升序)
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// 根据fetchRequest和managedObjectContext来创建aFetchedResultsController对象,并设置缓存名字为"Master".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Master"];
// 设置aFetchedResultsController的委托对象为当前类
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
// 获取第一批数据
if (![self.fetchedResultsController performFetch:&error])
{
// 错误处理
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return __fetchedResultsController;
}
因为我们设置了aFetchedResultsController的委托NSFetchedResultsControllerDelegate,就要实现它的方法,幸运的是这些方法看起来都像下面这样,直接复制粘贴就行了。
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type)
{
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}添加实体- (void)insertNewObject
{
// 从NSFetchedResultsController中获取NSManagedObjectContext对象
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
// 从NSFetchedResultsController中获取实体描述
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
// 在被管理上下文中插入一个新的NSManagedObject
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
// 字段赋值
[newManagedObject setValue:[NSDate date] forKey:@"timeStamp"];
// 保存被管理对象上下文,这样刚才的newManagedObject就被保存了
NSError *error = nil;
if (![context save:&error])
{
// 错误处理.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}删除实体// 从NSFetchedResultsController中获取NSManagedObjectContext对象
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
// 从被管理对象上下文中删除MO对象
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
// 保存被管理对象上下文
NSError *error = nil;
if (![context save:&error])
{
// 错误处理
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
这个删除对象为什么没有处理TableView的代码,因为我们实现NSFetchedResultsControllerDelegate委托,当对象上下文发生变化时NSFetchedResultsControllerDelegate的实现方法会处理这些事情。
这篇文章重点讲讲CoreData的Fetched Results Controller。
对应的objc类为NSFetchedResultsController。这个类是用来管理CoreData Fetch request返回的对象的。
在创建这个控制器之前,必须先创建fetch request。 fetch request描述了详细的查询规则,还可以添加查询结果的排序描述(sort descriptor)。fetchResultsController根据已经创建完的fetch request来创建, 它是NSFetchedResultsController的实例,这个实例的主要任务就是使用fetch request来保证它所关联的数据的新鲜性。创建了fetchResultsController实例后要做一下初始化,一般初始化是向这个控制器发送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;
- }
`这个函数用来创建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。控制器与数据源连接后,控制器监视器会时刻监视着数据源,当数据源发生
改变后,监视器会调用对应的协议方法,改协议总共要实现四个方法,分别为:
- - (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;
下面依次来解释这四个协议方法。
1. - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
当控制器监控的数据发生改变时,如对象被删除,有插入,更新等,监视器会在数据发生改变前意识到这个情况,此时就会调用这个函数。往往我们用列表的形式
表现数据,此时意味着屏幕上的数据即将过时,因为数据马上要改变了,这是这个协议方法的工作就是通知列表数据马上要更新的消息,往往代码是这样实现的。
- - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
- [self.tableView beginUpdates];
- }
2. - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
当fetchedResultsController完成对数据的改变时,监视器会调用这个协议方法。在上面提到的情况,这个方法要通知列表数据已经完成,可以更新显示的数据这个
消息,因此通常的实现是这样的。
- - (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发现指定的对象有改变时,监视器会调用这个协议方法。这里改变的类型从列表中体现有 更新、插入、删除或者行的移动。因此这个
方法要实现所有的这些方法,以应对任何一种改变。下面是这个方法的标准实现。
- - (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的变化,此时监视器就会调用这个协议函数。
下面是标准实现。
- - (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;
- }
- }