不知不觉我们已经来到了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 代替它:

@interface FBCDMasterViewController : UITableViewController

@property (nonatomic,strong) NSManagedObjectContext* managedObjectContext;
@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController; @end

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

@synthesize fetchedResultsController = _fetchedResultsController;

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

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

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

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

- (NSFetchedResultsController *)fetchedResultsController {

    if (_fetchedResultsController != nil) {
return _fetchedResultsController;
} NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:@"FailedBankInfo" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity]; NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:@"details.closeDate" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]]; [fetchRequest setFetchBatchSize:20]; NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName:@"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self; return _fetchedResultsController; }

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

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

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

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

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

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

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

- (void)viewDidLoad {
[super viewDidLoad]; NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
exit(-1); // Fail
} self.title = @"Failed Banks"; }

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

之后,更新numberOfRowsInSection方法

- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
id sectionInfo =
[[_fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}

更新 cellForRowAtIndexPath 方法:

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
FailedBankInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = info.name;
cell.detailTextLabel.text = [NSString stringWithFormat:@"%@, %@",
info.city, info.state];
} - (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; // Set up the cell...
[self configureCell:cell atIndexPath:indexPath]; return cell;
}

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

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

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[self.tableView beginUpdates];
} - (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)controller:(NSFetchedResultsController *)controller didChangeSection:(id )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)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller has sent all current change notifications, so tell the table view to process all updates.
[self.tableView endUpdates];
}

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

SELECT 0, t0.Z_PK FROM ZFAILEDBANKINFO t0 LEFT OUTER JOIN
ZFAILEDBANKDETAILS t1 ON t0.ZDETAILS = t1.Z_PK
ORDER BY t1.ZCLOSEDATE DESC
total fetch execution time: 0.0033s for 234 rows. SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSTATE, t0.ZCITY,
t0.ZDETAILS FROM ZFAILEDBANKINFO t0 LEFT OUTER JOIN
ZFAILEDBANKDETAILS t1 ON t0.ZDETAILS = t1.Z_PK WHERE
t0.Z_PK IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
ORDER BY t1.ZCLOSEDATE DESC LIMIT 20
total fetch execution time: 0.0022s for 20 rows. SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSTATE, t0.ZCITY,
t0.ZDETAILS FROM ZFAILEDBANKINFO t0 LEFT OUTER JOIN
ZFAILEDBANKDETAILS t1 ON t0.ZDETAILS = t1.Z_PK WHERE
t0.Z_PK IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
ORDER BY t1.ZCLOSEDATE DESC LIMIT 20
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. HDU 1939 HE IS OFFSIDE

    He is offside! Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...

  2. Vue 使用Spread.js没有层级关系(隐藏与显示)

    Vue 使用Spread.js没有层级关系(隐藏与显示) 1.vue会给元素加一个监控属性.去掉 spread.js没有层级关系过半是column中值的问题

  3. ubuntu上安装ffmpeg

      安装包和主要步骤: 1. 首先安装系统基础环境 RHEL & CentOS 系列:yum install -y automake autoconf libtool gcc gcc-c++ ...

  4. Ubuntu14连接MySql报错“can't connect to local mysql server through socket '/var/run/mysqld/mysqld.sock'”

    起因:我在Ubuntu14 64位系统中安装mysql后,后来通过mysql -u用户名 -p密码 的命令连接 Mysql数据库时,报错"can't connect to local mys ...

  5. CodeForces 659D Bicycle Race (判断点是否为危险点)

    D - Bicycle Race Time Limit:1000MS     Memory Limit:262144KB     64bit IO Format:%I64d & %I64u S ...

  6. wget下载整个网站的方法

    转自: http://blog.itpub.net/29867/viewspace-716088/  (修改部分内容) wget --restrict-file-name=ascii -m -c -n ...

  7. VS2013 MFC C++ CString ,const char , char, string 类型转换

    VS2013 测试 以下测试加入头文件: # include <string>#include <cstdlib>using namespace std; //-------- ...

  8. dict和set背后的实现原理

    # 先说结论 ''' dict的性能远大于list 在list中,随着数据的增大,时间也会增大 在dict中,随着数据的增大,时间没有变化 ''' # 目的:我们研究为什么dict的性能远大于list ...

  9. chanme的博客搬家了!

    一直以来都想自己租一台服务器,买个域名做一个自己的博客,但是由于时间和知识的关系,以前还不太知道怎么搭一个博客.终于我在上个礼拜成功的迈出了建站的第一步,然后陆陆续续的也将一些后续的步骤做好了.所以今 ...

  10. 第二部分:Spring中配置mongodb

    一.需要引用的jar包 1.spring-data-mongodb-1.9.4.RELEASE.jar 2.spring-data-commons-1.12.11.RELEASE.jar 3.mong ...