【IOS开发基础系列】NSFetchedResultsController专题

fetchedresults 控制器 提供下面3种模式:

    1.不追踪模式:delegate设为nil. 只提供基本的查询数据访问数据的能力。

    2.内存追踪模式:delegate有值,file cache name设为nil. controller负责监控结果集中的数据改变,针对改变调整排序。

    3.     完全追踪模式:delegate和file cache name都有值。controller负责监控结果集中的数据改变,针对改变调整排序。还能把计算的结果存入缓存。

        重要:作为这个类的delegate,至少要实现一个追踪方法:controllerDidChangeController。哪怕这个方法你什么也不写为空,也要实现这个方法。


1 创建控制器

        一般来说,你会创建一个NSFetchedResultsController实例作为tableview的成员变量。初始化的时候,你提供四个参数:

    1、 一个fetch request.必须包含一个sort descriptor用来给结果集排序。

    2、 一个managed object context。 控制器用这个context来执行取数据的请求。

    3、 一个可选的key path作为section name。控制器用key path来把结果集拆分成各个section。(传nil代表只有一个section)。

    4、 一个cachefile的名字,用来缓冲数据,生成section和索引信息。


        当你实例都创建好之后,调用performFetch:来执行查询。

NSManagedObjectContext *context = <#Managed object context#>;

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

//Configure the request's entity, and optionally its predicate.

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey: @"<#Sortkey#>" ascending: YES];

NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor, nil];

[fetchRequest setSortDescriptors: sortDescriptors];

[sortDescriptors release];

[sortDescriptor release];

NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest: fetchRequest managedObjectContext: context sectionNameKeyPath: nil cacheName: @"<#Cachename#>"];

[fetchRequest release];

NSError *error;

BOOL success = [controller performFetch: &error];


        重要:你不可以用一个fetched results controller来执行几个查询,如果你非要这么做,那用deleteCacheWithName:把cache file先删除,不然会冲突.


2 控制器的代理

        如果你为fetched results控制器设置了代理,代理会收到从它的managed object context中传来改变通知。delegate会处理context中任何会影响结果集或者section的变化,results也做相应的更新。控制器会告诉delegate结果集改变了什么或者section变化了哪些。你只要覆写几个方法来更新tableview就行。


3 缓存

        如果有必要,控制器会使用缓存来避免重复地构造section或者把section中的内容进行排序的工作。可以说,你程序一运行,这个缓存也跟着建立好了。

        当你初始化一个NSFetchedResultsController实例的时候,你一般会确定好文件的名字。(如果你不定义名字,那控制器就不使用缓存)当你创建一个控制器,它会寻找一个已存在的缓存:

        如果控制器找不到合适的缓存,它就计算section以及其内容的显示顺序。把结果信息写入硬盘。

        如果它能找到一个同名缓存,会先检查缓存中的数据是否合法。控制器会参照当前entity的名字,entity版本信息,排序,缓存文件更改时间来判断这个缓存是否可用。

        如果缓存和当前信息相兼容,控制器会复用之前存储的计算结果。

        如果缓存和当前信息不兼容,控制器重新计算然后更新缓存。

        一旦section和排序的信息改变了,那缓存一定会跟着改变。所以你如果有多个fetchedresult控制器,配置得都不一样,那你最好用不用得缓存名字。

        函数 deleteCacheWithName:可以直接删除一个缓存文件。

        实现tableview datasource方法,你可以让控制器提供相应的信息,然后实现你datasource方法。

- (NSInteger) numberOfSectionsInTableView: (UITableView *)tableView {

    return [[<#Fetched results controller#> sections] count];

}

- (NSInteger) tableView: (UITableView *)table numberOfRowsInSection: (NSInteger)section {

    id  sectionInfo = [[<#Fetched results controller#> sections] objectAtIndex: section];

    return [sectionInfo numberOfObjects];

}

- (UITableViewCell *) tableView: (UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    UITableViewCell *cell = <#Get the cell#>;

    NSManagedObject *managedObject = [< #Fetched results controller#> objectAtIndexPath: indexPath];

    //Configure the cell with data from the managed object.

    return cell;

}

- (NSString *) tableView: (UITableView *)tableView titleForHeaderInSection: (NSInteger)section {

    id  sectionInfo = [[<#Fetched results controller#> sections] objectAtIndex: section];

    return [sectionInfo name];

}

- (NSArray *) sectionIndexTitlesForTableView: (UITableView *)tableView {

    return [<#Fetched results controller#> sectionIndexTitles];

}

- (NSInteger) tableView: (UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex: (NSInteger)index{

    return [<#Fetched results controller#> sectionForSectionIndexTitle: title atIndex: index];

}


4 响应变化

        基本上,NSFetchedResultsController设计之初就是为了应对模型层的变化,负责告知其delegate结果变化了或者section的改变了。

        如果你允许一个用户重新对表格进行排序,那你实现的delegate方法就必须把这个也考虑进来。

        其实, managed object context收到processPendingChanges消息的时候,所作的变化才有所反应。因此,如果你对一个managedobject的属性进行了更改,存储位置变化了,控制器的结果会跟着改变。但当前事件周期结束,记录的索引才跟着变化(processPendingChanges函数被调用)。比如,下面这段代码 就应该输出“same”:

NSFetchedResultsController *frc = <#A fetched results controller#>;

NSManagedObject *managedObject = < #A managed object in frc's fetchedObjects array#>;

NSIndexPath *beforeIndexPath = [frc indexPathForObject: managedObject];

[managedObject setSortKeyAttribute: <#A new value that changes managedObject's position in frc's fetchedObjects array#>;

NSIndexPath *afterIndexPath = [frc indexPathForObject: managedObject];

if([beforeIndexPath compare:afterIndexPath] == NSOrderedSame) {

    NSLog(@"same");

}


4.1 改变查询请求

        你不能只是简单地改变fetchrequest来试图改变查询结果,如果你想看到变化,你应该:

    1、如果你用cache,删掉它(用deleteCacheWithName:函数)。不过一般来说,查询请求都改变了,你不太可能会用一个cache。

    2、改变fetch request。

    3、调用 performFetch:函数。


4.2 对象的检查

        如果一个managedobject context 告诉fetchedresult 控制器 一个单独的对象是无效的,那控制器就认为这样地对象已经被删除了,并且发送适当的delegate消息。

        有的时候可能所有的对象都是无效的(比如,当我们调用reset函数或者一个存储从持久化存储管理器中被删除的时候)NSFetchResultsController不会一个个对象去检验,也不会发送删除对象的通知。而是,你必须调用performFetch:去重置管理器并且重载tableview当中的数据(reloadData)。


4.3 iOS 版本的问题

        iOS 4.0以后,即使你重用一个cache,那管理器也不会报错,只是显示出错误的数据而已(这样可以削减程序启动时复杂的字符串比较,提高效率)。所以注意不要重复使用!


4.4 子类需要注意

        如果你想自定义section的创建,索引的标题,那你可能会子类这个控制器。你可以覆写sectionIndexTitleForSectionName:方法。

        如果你想section的名字不是大写字母开头,而是其他的,你可以覆写sectionIndexTitles。

        如果你想索引的名字是其他的内容,为每个已知section覆写sectionIndexTitleForSectionName:。


5 参考资料

iOS 7 SDK:如何使用后台获取(Background Fetch)

http://www.cocoachina.com/applenews/devnews/2013/1114/7350.html

[ios]NSFetchResultController与UICollectionView发出与索引/细胞的更新和删除操作

http://www.itstrike.cn/Question/658e4318-066e-456f-9815-c90c8d78ca83.html

你可能感兴趣的:(【IOS开发基础系列】NSFetchedResultsController专题)