本文将简单分析NSFetchedResultsController这个控制器类的用法和开源控件DEFetchRC的使用方法,文中用于讲解的代码片断,并不能直接粘贴使用,具体细节能够下载完整代码。git
若是您在阅读本文时,对CoreData尚未任何基础概念,这里给您推荐一套来自objcio.cn的CoreData系列教程,相信能对您有所帮助。github
从传统意义的MVC到MVVC的转变,相信你们都厌烦了术语套话,这些设计模式概念用天然语言描述显得十分抽象,咱们今天用Cocoa的类来简单描述一下这两种模式。数据库
刚入门Cocoa时,咱们接触到的最基本的类,就是UIViewController和UIView类,他们对应的MVC模型中的Controller和View,Model(数据模型)则能够是CoreData的数据,网络请求的数据,或者其余一些格式的数据,MVC的运做模式就是Controller将Model内容填入View,View响应交互让Controller处理数据而且修改View。segmentfault
通过实战咱们发现,Controller的代码量庞大不堪,一方面操做数据,一方面操做View,臃肿不堪,难以维护。因此MVVC模型应运而生,在MVC的基础上,加入了ViewModel概念,这个(些)ViewModel能够做为Controller的属性,剥离Controller中复杂的任务,例如:设计模式
替Controller实现TableView和CollectionView的回调。网络
剥离数据库操做app
剥离网络请求模块化
这样让Controller专一于处理View布局、交互和动画,把任务剥离,下降总体项目的耦合性。布局
基于高度模块化和低耦合性的设计趋势,NSFetchedResultsController
类应运而生。
你可能还不知道这个类能够完成什么工做,头文件中如此描述:fetch
This class is intended to efficiently manage the results returned from a Core Data fetch request. 这个类用于高效地响应CoreData数据变化
本文的Demo代码见zsy78191/DEFetchRC
初始化代码,这段代码摘自苹果官方模版,咱们自建DETableViewFetchRC
类用于绑定,这里在DETableViewFetchRC类中实现NSFetchedResultsController相关方法,使用的参数包括
self.entityName 实体名称
self.managedContext CoreData的上下文
self.predicate 检索谓语
self.sortKey 排序键
self.ascending 排序升序或降序
_fetchController.delegate NSFetchedResultsControllerDelegate委托
@property (nonatomic, strong) NSFetchedResultsController* fetchController; - (NSFetchedResultsController *)fetchController { if (_fetchController != nil) { return _fetchController; } NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // Edit the entity name as appropriate. NSEntityDescription *entity = [NSEntityDescription entityForName:self.entityName inManagedObjectContext:self.managedContext]; [fetchRequest setEntity:entity]; // Set the batch size to a suitable number. [fetchRequest setFetchBatchSize:20]; [fetchRequest setPredicate:self.predicate]; // Edit the sort key as appropriate. NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:self.sortKey ascending:self.ascending]; NSArray *sortDescriptors = @[sortDescriptor]; [fetchRequest setSortDescriptors:sortDescriptors]; // Edit the section name key path and cache name if appropriate. // nil for section name key path means "no sections". _fetchController= [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedContext sectionNameKeyPath:nil cacheName:nil]; _fetchController.delegate = self; NSError *error = nil; if (![_fetchController performFetch:&error]) { // Replace this implementation with code to handle the error appropriately. // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. ////LOGMARK(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return _fetchController; }
绑定好的NSFetchedResultsController经过KVO观察CoreData上下文变化,并通知咱们修改View,具体实现以下:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [[self.fetchController sections] count]; }
注:NSFetchedResultsController是支持TableView的Section功能的,咱们在这个例子中国年并无使用,读者能够根据须要进行修改。
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchController sections][section]; return [sectionInfo numberOfObjects]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if([self checkDataSourceCanResponse:@selector(tableView:cellAtIndexPath:withData:)]) { NSManagedObject *data = [self.fetchController objectAtIndexPath:indexPath]; //在这里处理Cell内容 } return nil; }
这里NSFetchedResultsController
提供了NSFetchedResultsControllerDelegate协议,用于绑定Model和View,以下。
#pragma mark - fetch - (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:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationLeft]; break; default: return; } } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(NSManagedObject*)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { UITableView *tableView = self.tableView; switch(type) { case NSFetchedResultsChangeInsert: [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeDelete: [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft]; break; case NSFetchedResultsChangeUpdate: [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeMove: [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { [self.tableView endUpdates]; }
至此,NSFetchedResultsController的基本使用方法都介绍了,接下来介绍一下我作的一个对NSFetchedResultsController的简单封装,zsy78191/DEFetchRC。
一个NSFetchedResultsController的简单封装
DETableViewFetchRC将自动绑定CoreData的ManageContext和TableView,绑定好之后,数据的任何变更,包括增删改查,都会自动调用对应Cell的更新,不须要本身写任何代码作通知挂钩等。
Demo中使用Apple的CoreData模版,CoreData的Context在AppDelegate中。
- (NSManagedObjectContext*)managedObjectContext { AppDelegate* delegate = [UIApplication sharedApplication].delegate; return delegate.managedObjectContext; }
@property (nonatomic, strong) DETableViewFetchRC* tableFetchResultController; - (void)viewDidLoad { [super viewDidLoad]; [self.managedObjectContext setUndoManager:[[NSUndoManager alloc] init]]; self.tableFetchResultController = [DETableViewFetchRC fetchRCWithView:self.tableView reciver:self managedContext:self.managedObjectContext entityName:@"Item" sortKey:@"date" ascending:YES predicate:nil]; }
其中第一参数输入须要绑定(bind)的 TableView(若是使用DECollectionViewFetchRC则传入CollectionView),第二个参数是DETableViewFetchDelegate的委托,主要用来实现Cell内容填充回调:
- (UITableViewCell *)tableView:(UITableView *)tableView cellAtIndexPath:(NSIndexPath *)indexPath withData:(id)data { Item* item = data; UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; cell.textLabel.text = item.title; cell.detailTextLabel.text = [item.date description]; return cell; }
这里的CoreData实例名字是Item,有title和date两个属性,分别为NSString和NSDate。
这样就完成了一步绑定,CoreData的Context有变更时自动更新调用Cell更新回调,更新Cell,增删改都自动完成。效果以下:
详细的代码请参考zsy78191/DEFetchRC
本文做为怎样下降iOS代码耦合性一文的延伸阅读,介绍了NSFetchedResultsController
类的基本使用方法,这个类做为CoreData的辅助控制器,在哲学高度强化了CoreData和TableView的绑定,原理上也适用于CoreData和CollectionView或其它第三方控件的绑定,灵活中不失稳重,值得细细研究一番。