不知不觉我们已经来到了Core Data系列教程的最后一部分了,在这里我们要讨论如何使用NSFetchedResultsController来优化我们的应用,提高应用的运行速度,减少其内存占用。

你是不是已经忘记了以前讲过什么呢?我们来复习一下,在第一篇教程中:《iOS教程:Core Data数据持久性存储基础教程》中我们讲了如何为一个iOS程序创建一个Core Data的数据模型和测试的方法,还有我们还把这个数据模型作为数据源连接到了一个表视图上。

在第二篇教程:《iOS教程:如何使用Core Data – 预加载和引入数据》,我们讲了如何解析不同格式的数据文件到一个Core Data通用的SQlite数据库中,还有如何将这个数据库移植到我们的iOS项目上去,好让我们的应用有一些初始数据。

你可以从这里下载第二部分的源码。

为什么要使用 NSFetchedResultsController?

到目前为止,我们就像在使用SQLite3的方法一样,因为本质上Core Data就是在操作SQLite数据库,但是我们写的代码比直接使用SQLite更少,我们使用各种数据库功能也更容易。

但是,我们还有一个很好用的Core Data特性没有用上,这个特性能够很大程度上的提高我们程序的性能,他就是:NSFetchedResultsController。

现在在我们的例子程序中,我们都是一下再将所有的数据全部加载进视图,对于我们的这个应用而言,这也许是可以接受的,但是如果一个应用有大量的数据,载入速度就会变得很慢,也会给用户体验造成影响。

在理想的情况下,我们只载入用户正在浏览的那一部分的数据,幸运的是,苹果官方已经提供了一个这样做的方法,就是NSFetchedResultsController。

所以,咱们先打开 FBCDMasterViewController.h,把之前的failedBankInfos,这个NSArray数组闪电,加入一个NSFetchedResultsController 代替它:

  1. @interface FBCDMasterViewController : UITableViewController
  2.  
  3. @property (nonatomic,strong) NSManagedObjectContext* managedObjectContext;
  4. @property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
  5.  
  6. @end

在FBCDMasterViewController.m的synthesize部分,删除以前的failedBankInfos synthesize声明,并且加入:

  1. @synthesize fetchedResultsController = _fetchedResultsController;

另一个 NSFetchedResultsController 很酷的特性是你可以在ViewDidUnload中将它重新声明为nil。这意味着这个方法有一个自动的内存管理机制,也就是说当内容不在屏幕之中后,其占用内存会自动被清空。要完成这一切,你所需做的就是在ViewDidUnload中将它声明为空。

  1. - (void)viewDidUnload {
  2. self.fetchedResultsController = nil;
  3. }

好了,现在到了有趣的部分了,我们开始创建取得的数据的控制器。首先我们声明一个属性,让它随着程序的运行检测取得数据是否存在,如果不存在就创造之。

在文件的头部加入以下代码:

  1. - (NSFetchedResultsController *)fetchedResultsController {
  2.  
  3. if (_fetchedResultsController != nil) {
  4. return _fetchedResultsController;
  5. }
  6.  
  7. NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
  8. NSEntityDescription *entity = [NSEntityDescription
  9. entityForName:@"FailedBankInfo" inManagedObjectContext:managedObjectContext];
  10. [fetchRequest setEntity:entity];
  11.  
  12. NSSortDescriptor *sort = [[NSSortDescriptor alloc]
  13. initWithKey:@"details.closeDate" ascending:NO];
  14. [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
  15.  
  16. [fetchRequest setFetchBatchSize:20];
  17.  
  18. NSFetchedResultsController *theFetchedResultsController =
  19. [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
  20. managedObjectContext:managedObjectContext sectionNameKeyPath:nil
  21. cacheName:@"Root"];
  22. self.fetchedResultsController = theFetchedResultsController;
  23. _fetchedResultsController.delegate = self;
  24.  
  25. return _fetchedResultsController;
  26.  
  27. }

这段代码和我们在 viewDidLoad 方法中使用的很是相似,创建一个fetch请求,将FailedBankInfo物体引出等等,但是仍然有一些新的东西我们要讨论。

首先,当我们使用NSFetchedResultsController,,我们必须设置一个数据分类器来赋给fetch请求,数据分类器就是我们告诉Core Data我们希望引入的数据被褥和储存的方法。

这种数据分类器存在的必要就在于它不仅能够编排所有返回的属性和数据,还可以编排与之相关的所有属性和数据,就好像一个天才的人在做这些事情一样。如果我们想要根据FailedBankDtails中的close date这个属性编排数据,但却想要接收FailedBankInfo中的所有数据,Core Data通过这个特性就可以完成这样的事情。

下一个声明十分的重要,就是设置取得的数据的缓冲值的最大值,这正是我们在这个场景中想要使用这种特性的原因,这样的话,fetched方法就会自动取得设置的值的数据项目,之后把当我们向下查看的时候程序就会自动取得各种数据。

当我们设置好这个fetch的缓冲值的时候,我们就完成了创建 NSFetchedRequestController 并且将它传递给了fetch请求,但是这个方法其实还有以下几个参数:

  • 对于managed object 内容,我们值传递内容。
  • section name key path允许我们按照魔种属性来分组排列数据内容。
  • 文件名的缓存名字应该被用来处理任何重复的任务,比如说设置分组或者排列数据等。

现在我们已经完全创建好了一个取得部分数据的方法,我们下面修改一下以前使用数据加入数据的方法,让它使用我们取得的数据。

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3.  
  4. NSError *error;
  5. if (![[self fetchedResultsController] performFetch:&error]) {
  6. // Update to handle the error appropriately.
  7. NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
  8. exit(-1); // Fail
  9. }
  10.  
  11. self.title = @"Failed Banks";
  12.  
  13. }

我们在这里来操作我们的 fetchedResultsController 并且执行performFetch 方法来取得缓冲的第一批数据。

之后,更新numberOfRowsInSection方法

  1. - (NSInteger)tableView:(UITableView *)tableView
  2. numberOfRowsInSection:(NSInteger)section {
  3. id sectionInfo =
  4. [[_fetchedResultsController sections] objectAtIndex:section];
  5. return [sectionInfo numberOfObjects];
  6. }

更新 cellForRowAtIndexPath 方法:

  1. - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
  2. FailedBankInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath];
  3. cell.textLabel.text = info.name;
  4. cell.detailTextLabel.text = [NSString stringWithFormat:@"%@, %@",
  5. info.city, info.state];
  6. }
  7.  
  8. - (UITableViewCell *)tableView:(UITableView *)tableView
  9. cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  10.  
  11. static NSString *CellIdentifier = @"Cell";
  12.  
  13. UITableViewCell *cell =
  14. [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  15.  
  16. // Set up the cell...
  17. [self configureCell:cell atIndexPath:indexPath];
  18.  
  19. return cell;
  20. }

现在我们将之前的逻辑分为一些分开的 configureCell 方法,我们待会会用到。

还有最后一件事情,我们需要为 NSFetchedResultsController设置一个代理方法,好消息是都有模版,其实是我从apple官方的一个例子程序中copy过来的,将以下方法加入在文件的底部:

  1. - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
  2. // The fetch controller is about to start sending change notifications, so prepare the table view for updates.
  3. [self.tableView beginUpdates];
  4. }
  5.  
  6. - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
  7.  
  8. UITableView *tableView = self.tableView;
  9.  
  10. switch(type) {
  11.  
  12. case NSFetchedResultsChangeInsert:
  13. [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
  14. break;
  15.  
  16. case NSFetchedResultsChangeDelete:
  17. [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
  18. break;
  19.  
  20. case NSFetchedResultsChangeUpdate:
  21. [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
  22. break;
  23.  
  24. case NSFetchedResultsChangeMove:
  25. [tableView deleteRowsAtIndexPaths:[NSArray
  26. arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
  27. [tableView insertRowsAtIndexPaths:[NSArray
  28. arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
  29. break;
  30. }
  31. }
  32.  
  33. - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
  34.  
  35. switch(type) {
  36.  
  37. case NSFetchedResultsChangeInsert:
  38. [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
  39. break;
  40.  
  41. case NSFetchedResultsChangeDelete:
  42. [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
  43. break;
  44. }
  45. }
  46.  
  47. - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
  48. // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
  49. [self.tableView endUpdates];
  50. }

现在编译运行你的应用的话,表面上看起来应该都是一样的,但是如果你看看控制台的话,惊人的事情正在发生哦:

  1. SELECT 0, t0.Z_PK FROM ZFAILEDBANKINFO t0 LEFT OUTER JOIN
  2. ZFAILEDBANKDETAILS t1 ON t0.ZDETAILS = t1.Z_PK
  3. ORDER BY t1.ZCLOSEDATE DESC
  4. total fetch execution time: 0.0033s for 234 rows.
  5.  
  6. SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSTATE, t0.ZCITY,
  7. t0.ZDETAILS FROM ZFAILEDBANKINFO t0 LEFT OUTER JOIN
  8. ZFAILEDBANKDETAILS t1 ON t0.ZDETAILS = t1.Z_PK WHERE
  9. t0.Z_PK IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
  10. ORDER BY t1.ZCLOSEDATE DESC LIMIT 20
  11. total fetch execution time: 0.0022s for 20 rows.
  12.  
  13. SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSTATE, t0.ZCITY,
  14. t0.ZDETAILS FROM ZFAILEDBANKINFO t0 LEFT OUTER JOIN
  15. ZFAILEDBANKDETAILS t1 ON t0.ZDETAILS = t1.Z_PK WHERE
  16. t0.Z_PK IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
  17. ORDER BY t1.ZCLOSEDATE DESC LIMIT 20
  18. total fetch execution time: 0.0017s for 20 rows.

你可以看到,在背后, NSFetchedResultsController 正在从 FailedBankInfo中庸之前设置的顺序取得大量的ID,每次只缓冲一定数量的项目,就像我们预料的一样。

如果直接使用SQLite数据库的话,就会有很多工作要做了,我们何不使用Core Data节省时间呢。

之后看些什么?

这是我制作完成的例子程序源码,欢迎下载。

这是原作者的样板程序:project here (direct download).

欢迎关注我的围脖: @Oratis

在知乎和豆瓣上,我的名字也是Oratis

我会把之后发表的教程分享到这些社交网络中。

如果你有任何问题,欢迎在底下留言,也欢迎写信给我,我的邮箱地址是: oratis@appkon.com

iOS教程:如何使用NSFetchedResultsController的更多相关文章

  1. iOS教程:如何使用Core Data – 预加载和引入数据

    这是接着上一次<iOS教程:Core Data数据持久性存储基础教程>的后续教程,程序也会使用上一次制作完成的. 再上一个教程中,我们只做了一个数据模型,之后我们使用这个数据模型中的数据创 ...

  2. 最详细在Windows安装Xamarin.iOS教程

    最详细在Windows安装Xamarin.iOS教程 来源:http://www.cnblogs.com/llyfe2006/articles/3098280.html 本文展示了如何设立Xamari ...

  3. Xamarin iOS教程之键盘的使用和设置

    Xamarin iOS教程之键盘的使用和设置 Xamarin iOS使用键盘 在文本框和文本视图中可以看到,当用户在触摸这些视图后,就会弹出键盘.本节将主要讲解键盘的输入类型定义.显示键盘时改变输入视 ...

  4. Xamarin iOS教程之显示和编辑文本

    Xamarin iOS教程之显示和编辑文本 Xamarin iOS显示和编辑文本 在一个应用程序中,文字是非常重要的.它就是这些不会说话的设备的嘴巴.通过这些文字,可以很清楚的指定这些应用程序要表达的 ...

  5. Xamarin iOS教程之视图显示图像

    Xamarin iOS教程之视图显示图像 Xamarin iOS显示图像 在主视图中显示一个图像,可以让开发者的应用程序变的更有趣,例如,在一些应用程序开始运行时,都会通过图像来显示此应用程序的玩法或 ...

  6. Xamarin iOS教程之使用按钮接接收用户输入

    Xamarin iOS教程之使用按钮接接收用户输入 Xamarin iOS使用按钮接接收用户输入 按钮是用户交互的最基础控件.即使是在iPhone或者iPad中,用户使用最多操作也是通过触摸实现点击. ...

  7. Xamarin iOS教程之添加和定制视图

    Xamarin iOS教程之添加和定制视图 Xamarin iOS用户界面——视图 在iPhone或者iPad中,用户看到的摸到的都是视图.视图是用户界面的重要组成元素.例如,想要让用户实现文本输入时 ...

  8. Xamarin iOS教程之申请付费开发者账号下载证书

    Xamarin iOS教程之申请付费开发者账号下载证书 Xamarin iOS使用真机测试应用程序 在讲解iOS Simulator时,已经提到了虽然iOS Simulator可以模仿真实的设备,但是 ...

  9. Xamarin iOS教程之编辑界面编写代码

    Xamarin iOS教程之编辑界面编写代码 Xamarin iOS的Interface Builder Interface Builder被称为编辑界面.它是一个虚拟的图形化设计工具,用来为iOS应 ...

随机推荐

  1. iOS runLoop 理解

    目录 概述 run loop modes 一.概述 run loop叫事件处理循环,就是循环地接受各种各样的事件.run loop是oc用来管理线程里异步事件的工具.一个线程通过run loop可以监 ...

  2. 新浪微博 page应用 自适应高度设定 终于找到解决方法

    我做的是PAGE应用,无法自适应高度.找了好久解决方法. 用js 设置父窗口 iframe 也不好用,有的浏览器不兼容. 官方上说发是这样的: 应用动态高度自适应 Iframe高度:开发者可以使Ifr ...

  3. android中常见的Drawable资源有哪些?

    Drawable资源是安卓应用中最常见的一种资源,比如图片等,因此,对于初学者而言,必须掌握drawable资源相关应用. 今天在网上刚好看到了一篇介绍android Drawable资源的文章,分享 ...

  4. [CF954G]Castle Defense

    题目大意:有$n$个点,每个点最开始有$a_i$个弓箭手,在第$i$个位置的弓箭手可以给$[i-r,i+r]$区间加上$1$的防御,你还有$k$个弓箭手,要求你最大化最小防御值 题解:二分答案,从右向 ...

  5. 熊猫(i)

    题目描述 熊猫喜欢吃数,熊猫对与每个数都有他独特的评价.具体来说,熊猫对数 xx 的评价是个四元组 (a, b, c, d)(a,b,c,d),计算方式如下: 首先将 xx 写成二进制形式(不含前导零 ...

  6. php的post

    代码的顺序不能乱,否则会提交错误 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HE ...

  7. NFS排错案例

    1.检验rpcinfo从客户端 # rpcinfo -p nfsserverip ,可以看到服务器端开的tcp/udp端口.默认都是打开的,客户端可以自己选择使用TCP/UDP program ver ...

  8. Linux 安装配置JDK

    一.下载jdk 参考:http://www.codingyun.com/article/40.html 可以先下载到本地,然后ftp到服务器 也可以直接在服务器下载(windows版本的区分32位与6 ...

  9. DataTable 去重合并

    //合并 dt.Merge(dt2); //去重 dt = dt.AsDataView().ToTable(true);

  10. 培训补坑(day3:网络流&最小割)

    继续补坑.. 第三天主要是网络流 首先我们先了解一下网络流的最基本的算法:dinic 这个算法的主要做法就是这样的: 在建好的网络流的图上从源点开始向汇点跑一遍BFS,然后如果一条边的流量不为0,那么 ...