不知不觉我们已经来到了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. Codeforces Round #326(Div2)

    CodeForces 588A 题意:Duff喜欢吃肉,想在接下来的n天,每天都有Ai斤肉吃,但每一天肉的单价Pi不定,肉 可以保存不过期,现已知n天每天肉的斤数Ai,以及单价Pi,为了使每天都   ...

  2. poj1273 网络流入门题 dinic算法解决,可作模板使用

    Drainage Ditches Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 62078   Accepted: 2384 ...

  3. tornado获取application/json类型的入参

    tornado本身是不支持直接获取json入参的,在BaseHandler中定义方法get_json_argument,以供调用 class BaseHandler(tornado.web.Reque ...

  4. Linux : 使用 lsof 恢复文件

    用 lsof 命令在某种程度上可以恢复删除的文件, 前提是这个文件被正在运行的进程占用. 比如: 日志文件, 配置文件. lsof 恢复文件 查找需要恢复的文件和占用文件的进程 PID lsof |g ...

  5. windows实时监测热插拔设备的变化(转)

    原文转自 https://blog.csdn.net/windows_nt/article/details/13614849 序: 在21世纪,这个信息时代,热插拔设备是一个巨大的安全隐患.在这个篇文 ...

  6. Cookies/Session机制详解

    # 转载自:https://blog.csdn.net/fangaoxin/article/details/6952954/ 会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话 ...

  7. PE文件RV转FOA及FOA转RVA

    /************************************************************************/ /* 功能:虚拟内存相对地址和文件偏移的转换 参数 ...

  8. 【转载】性能监视器(SSAS)

    使用性能监视器,您可以通过性能计数器监视 Microsoft SQL Server Analysis Services (SSAS) 实例的性能. 性能监视器是用于跟踪资源使用情况的 Microsof ...

  9. 清明小长假之VUE.JS学习测试码

    我们放了四天假,刚好借此机会,系统的了解一下VUE.JS. <!DOCTYPE html> <html> <head> <meta charset=" ...

  10. Delphi 的TSpeedButton按下和弹起效果

    想达到这样的效果: 点击一下TSpeedButton按钮,按钮凹下去,再点击一下,按钮弹起恢复. 实现方法: 只要设置下述2个属性即可,不需要编码: ①AllowAllUp = True ②Group ...