现在的位置: 首页 > 综合 > 正文

IOS NSFetchedResultsController和UITableView集成起来处理数据具有强大的灵活性

2013年12月07日 ⁄ 综合 ⁄ 共 6037字 ⁄ 字号 评论关闭

引子:NSFetchedResultsControllerUITableView集成起来处理数据具有强大的灵活性。首先得到的好处是不需要将数据记录进行分页,不然,按照传统的做法,需要先查询出总的记录,然后再从纪录里面过滤,这样会进行两次操作,对内存消耗很大,处理不好,程序甚至可能崩溃。使用NSFetchedResultsController类不仅简单,还具有更高的性能,这个类自动帮助你记录分页的事情,得到表对应的CoreData对象也非常简单。

更重要的是,你在其他界面更新或者删除记录时,NSFetchedResultsController可以帮助你同步更新UITableView,你的UITableView和数据库同步将变得非常简单。

 NSFetchedResultsController

继承自
遵循
框架
/System/Library/Frameworks/CoreData.framework
可用性
Available in iOS 3.0 and later.
指南
定义于
NSFetchedResultsController.h
相关代码

  当你从CoreData
取出数据,然后以表格视图的形式展现数据的时候,
fetched results
控制器很有用。它是连接CoreData和表格视图的桥梁,首选的一种控制器。


  (图一)

我们知道表格视图有很多种形式,fetched results
控制器主要针对列表视图(如图1),这种视图是由很多section组成的,每个section又包含了很多row。你只需配置:entity是什么,如何排序(必须配置),预筛选项(可选)。NSFetchedResultsController就会自动帮你处理好每个section都应该显示哪些数据。

 

另外,NSFecthedResultsController还提供以下特性:

  • 监控managedobject context对象的改变,报告给delegate(可选).
  • 缓冲数据,让性能有所提升,专门针对需要频繁显示的数据 (可选)。

 

总结一下,fetchedresults
控制器 提供下面3种模式:

1.     不追踪模式:delegate设为nil.

只提供基本的查询数据访问数据的能力。

2.     内存追踪模式:delegate有值,filecache name设为nil.

controller负责监控结果集中的数据改变,针对改变调整排序。

    3.     完全追踪模式:delegatefilecache name都有值

    controller负责监控结果集中的数据改变,针对改变调整排序。还能把计算的结果存入缓存。

 

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

 

使用NSFetchedResultsController控制器

 

创建控制器

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

1
一个
fetchrequest.必须包含一个sortdescriptor用来给结果集排序。

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

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

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

 

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

 

NSManagedObjectContext *context <#Managed object context#>;
NSFetchRequest *fetchRequest [[NSFetchRequest alloc] init];
//Configure the request's entity, and optionally itspredicate.
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];

 

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

 

控制器的代理

 

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

 

缓存

 

如果有必要,控制器会使用缓存来避免重复地构造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 <NSFetchedResultsSectionInfo> 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 managedobject.
    
return cell;
}

(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 
    id <NSFetchedResultsSectionInfo> 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];
}

 

响应变化

 

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

 

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

 

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

 

NSFetchedResultsController *frc <#fetched results controller#>;
NSManagedObject *managedObject <
#managed object in frc'fetchedObjects array#>;
NSIndexPath *beforeIndexPath [frc indexPathForObject:managedObject];
[managedObject setSortKeyAttribute:
                  <
#new value that changes managedObject'position in frc'fetchedObjects array#>;
NSIndexPath *afterIndexPath [frc indexPathForObject:managedObject];
if ([beforeIndexPath compare:afterIndexPath] == NSOrderedSame) {
    NSLog(
@"same");
}

 

改变查询请求

 

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

1。如果你用cache,删掉它(用deleteCacheWithName:函数)

不过一般来说,查询请求都改变了,你不太可能会用一个cache.

2。改变fetchrequest.

3。调用
performFetch:
函数

 

对象的检查

 

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

 

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

 

iOS 版本的问题

 

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

 

子类需要注意

 

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

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

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

抱歉!评论已关闭.