我们知道 CoreData 里存储的是具有相同结构的一系列数据的集合,TableView 正好是用列表来展示一系列具有相同结构的数据集合的。所以,要是 CoreData 和 TableView 能结合起来,CoreData 查询出来的数据能同步地显示在 TableView 上,更好一点就是 CoreData 里的改动也能同步到 TableView 上,那就再好不过了。可喜的是,确实有这样一个 API,那就是 NSFetchedResultsController,相信不少人对这个东西都不陌生,因为用 Xcode 创建带有 CoreData 的 Master-Detail 模板工程时,就是用这个接口来实现的。这篇文章也主要是围绕着模板工程中的代码进行介绍,如果你对这块比较熟悉的话,不妨直接去看模板里的代码;如果你是第一次听说这个 API,不妨继续看下去,相信会对你有帮助的。

创建一个简单的 TableView 布局

在使用 CoreData 之前,首先我们来创建一个简单的 TableView 布局,对大多数人来说,这应该没什么难度,所以下面就直接贴代码,不会对代码进行解释了。
这里我们用 Storyboard 创建一个 TableViewController,首先配置 tableView 的 dataSource

- (void)viewDidLoad {[super viewDidLoad];// 添加编辑和插入按钮self.navigationItem.leftBarButtonItem = self.editButtonItem; self.navigationItem.rightBarButtonItem = [self addBarButtonItem]; } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 100; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier forIndexPath:indexPath]; [self configureCell:cell atIndexPath:indexPath]; return cell; } - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { return YES; } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { // deletions } }

这是一个非常简单的 TableView,现在只能显示一些随机生成的数据。接下来我们来实现 NSFetchedResultsController 来和 CoreData 中的数据对接,CoreData 相关的代码,就直接用之前文章里创建的 Student 实体。如果有不了解的朋友,可以先去看一下这篇文章 CoreData 从入门到精通 (一) 数据模型 + CoreData 栈的创建。

初始化 NSFetchedResultsController

先来看一下 NSFetchedResultsController 的初始化代码:

#pragma mark - NSFetchedResultsController- (NSFetchedResultsController<Student *> *)fetchedResultsController {if (!_fetchedResultsController) {NSFetchRequest *fetchRequest = [Student fetchRequest];fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"studentId" ascending:YES]]; fetchRequest.fetchBatchSize = 50; fetchRequest.fetchLimit = 200; NSFetchedResultsController *fetchController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.context sectionNameKeyPath:@"studentAge" cacheName:@"StudentTable"]; fetchController.delegate = self; NSError *error; [fetchController performFetch:&error]; [[[Logger alloc] init] dealWithError:error whenFail:@"fetch failed" whenSuccess:@"fetch success"]; _fetchedResultsController = fetchController; } return _fetchedResultsController; }

创建 fetchedResultsController 需要指定一个 fetchRequest,这很好理解,因为 fetchedResultsController 也需要查询 CoreData 数据库里的数据,需要注意的是,指定的这个 fetchRequest 必须要设置 sortDescriptors 也就是排序规则这个属性,不设置直接运行的话,程序是会直接崩溃的,这是因为 fetchedResultsController 需要根据这个排序规则来规定数据该以什么顺序显示到 tableView 上,而且这个 fetchRequest 指定之后就不可以再修改了;

fetchRequest-w600

context 就是上下文的对象;sectionNameKeyPath 可以指定一个 keypath 来为 tableView 生成不同的 section,指定成 nil 的话,就只生成一个 section;

sectionNameKeyPath-w600

cacheName 用来指定一个缓存的名字,加载好的数据会缓存到这样一个私有的文件夹里,这样可以避免过多的从 CoreData 数据库里查询以及计算的操作。

cacheName-w600

除此之外,fetchLimitfetchBatchSize 这两个属性也需要注意一下,fetchLimit 之前讲过是指定获取数据的上限数量,而 fetchBatchSize 是分批查询的数据量大小。因为一次性查询出过多的数据会消耗不少的内存,所以这里推荐给这两个属性设置一个合理的值;指定的泛型 Student 就是 fetchRequest 查询的数据类型。这些都配置之后调用 performFetch: 方法就可以执行查询操作了。返回的数据保存在 fetchedResultsControllerfetchedObjects 属性里,不过我们一般不会直接用到它。

fetchedResultsController 绑定到 TableView

下面来修改 tableView 的 dataSource 的方法将查询出来的数据集合和 TableView 绑定。

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {// sections 是一个 NSFetchedResultsSectionInfo 协议类型的数组,保存着所有 section 的信息return self.fetchedResultsController.sections.count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // sectionInfo 里的 numberOfObjects 属性表示对应 section 里的结果数量 id<NSFetchedResultsSectionInfo> sectionInfo = self.fetchedResultsController.sections[section]; return sectionInfo.numberOfObjects; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier forIndexPath:indexPath]; // 通过这个方法可以直接获取到对应 indexPath 的实体类对象 Student *student = [self.fetchedResultsController objectAtIndexPath:indexPath]; [self configureCell:cell withStudent:student]; return cell; } // 修改后的configureCell 方法 - (void)configureCell:(UITableViewCell *)cell withStudent:(Student *)student { cell.textLabel.text = [NSString stringWithFormat:@"%d:%@ age:%d", student.studentId, student.studentName, student.studentAge]; }

实现增删改查的同步更新

上一步里我们实现了把 fetchedResultsController 里的数据绑定到 TableView 上,但还没完成同步更新的实现,例如 CoreData 数据库里新插入了数据,TableView 这时也可以自动更新。实现这个功能,只需要实现 fetchedResultsControllerdelegate 就可以了。

NSFetchedResultsControllerDelegate 里有一个 NSFetchedResultsChangeType 枚举类型,其中的四个成员分别对应 CoreData 里的增删改查:

typedef NS_ENUM(NSUInteger, NSFetchedResultsChangeType) {NSFetchedResultsChangeInsert = 1,NSFetchedResultsChangeDelete = 2, NSFetchedResultsChangeMove = 3, NSFetchedResultsChangeUpdate = 4 }

delegate 里共有五个协议方法:

// 对应 indexPath 的数据发生变化时会回调这个方法
@optional
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(nullable NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(nullable NSIndexPath *)newIndexPath; // section 发生变化时会回调这个方法 @optional - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type; // 数据内容将要发生变化时会回调 @optional - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller; // 数据内容发生变化之后会回调 @optional - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller; // 返回对应 section 的标题 @optional - (nullable NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName; @end

想要实现 tableView 的数据同步更新可以按下面的代码来实现这几个 delegate 方法:

#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {// 在这里调用 beginUpdates 通知 tableView 开始更新,注意要和 endUpdates 联用[self.tableView beginUpdates]; } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { // beginUpdates 之后,这个方法会调用,根据不同类型,来对tableView进行操作,注意什么时候该用 indexPath,什么时候用 newIndexPath. switch (type) { case NSFetchedResultsChangeInsert: [self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeMove: [self.tableView moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; break; case NSFetchedResultsChangeUpdate: [self configureCell:[self.tableView cellForRowAtIndexPath:indexPath] withStudent:anObject]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { // 更新完后会回调这里,调用 tableView 的 endUpdates. [self.tableView endUpdates]; }

最后来实现一开始添加的编辑和插入按钮的操作:

- (UIBarButtonItem *)addBarButtonItem {UIBarButtonItem *addBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addRandomStudent)]; return addBarButtonItem; } // 为了看到插入效果,可以把 fetchedResultsController 的fetchLimit 和 fetchBatchSize 调小一些. - (void)addRandomStudent { NSString *name = [NSString stringWithFormat:@"student-%u", arc4random_uniform(100000)]; int16_t age = (int16_t)arc4random_uniform(10) + 10; int16_t stuId = (int16_t)arc4random_uniform(INT16_MAX); Student *student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.context]; student.studentName = name; student.studentAge = age; student.studentId = stuId; [self.context save:nil]; } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { Student *student = [self.fetchedResultsController objectAtIndexPath:indexPath]; [self.context deleteObject:student]; [self.context save:nil]; } }

到此为止 NSFetchedResultsController 的使用就讲完了,有了它 CoreData 和 TableView的结合是不是很方便呢。

CoreData 从入门到精通(五)CoreData 和 TableView 结合相关推荐

  1. Kali Linux 从入门到精通(五)-测试环境准备

    Kali Linux 从入门到精通(五)-测试环境准备 准备实验环境 渗透非系统授权的弊端 搭建自己的实验环境 安装虚拟机 微软最新软件 http://msdn.microst.com/en-ca/s ...

  2. CoreData 从入门到精通 (一) 数据模型 + CoreData 栈的创建

    概述 CoreData 是 Cocoa 平台上用来管理模型层数据和数据持久化的一个框架,说简单点,就是一个数据库存储框架.CoreData 里相关的概念比较多,而且初始化也非常繁琐,所以对初学者的学习 ...

  3. CoreData 从入门到精通(四)并发操作

    通常情况下,CoreData 的增删改查操作都在主线程上执行,那么对数据库的操作就会影响到 UI 操作,这在操作的数据量比较小的时候,执行的速度很快,我们也不会察觉到对 UI 的影响,但是当数据量特别 ...

  4. CoreData 从入门到精通(三)关联表的创建

    上篇博客中讲了 CoreData 里增删改查的使用,学到这里已经可以应对简单的数据存储需求了.但是当数据模型复杂起来时,例如你的模型类中除了要存储 CoreData 里支持的数据类型外,还有一些自定义 ...

  5. Drf从入门到精通五(2个视图基类、5个视图拓展类、9个视图子类、视图集)

    文章目录 一.2个视图基类 1) 基于AIPView写5个接口 2) 基于GenericAPIView写5个接口 二.5个视图拓展类 1) 基于GenericAPIView+5个视图拓展类写接口 三. ...

  6. 【超级无敌详细的韩顺平java笔记】从入门到精通---五种运算符

    一.算术运算符 1.介绍 算术运算符是对数值类型的变量进行运算的 2. 演示算术运算符的使用 public class ArithmeticOperator { //编写一个 main 方法 publ ...

  7. 物联网Lora模块从入门到精通(五)光照与温湿度传感器

    一.前言 在程序开发中,光照与温湿度的获取是十分常见与重要的,本文我们主要是使用M21温湿度光照三合一传感器,其中温湿度数据通过协议获取,而光照通过ADC获取. 二.代码实现 本文内容较为简单,且后续 ...

  8. Spark SQL:从入门到精通(五)[开窗函数]

    概述 https://www.cnblogs.com/qiuting/p/7880500.html 介绍: 开窗函数的引入是为了既显示聚集前的数据,又显示聚集后的数据.即在每一行的最后一列添加聚合函数 ...

  9. C4D致富经典入门到精通(十)

    C4D中渲染操作与AE交互 C4D基础界面的介绍与常用快捷键:  C4D致富经典入门到精通(一) C4D父子关系的理解与创建参数几何体与可编辑对象: C4D致富经典入门到精通(二) C4D样条曲线创建 ...

最新文章

  1. 在CentOS 6.9 x86_64的OpenResty 1.13.6.1上使用基于Redis实现动态路由示例
  2. leecode---46---数组,dfs---求出数组的所有组合
  3. 动态加载flex皮肤.
  4. FastDFS分布文件系统
  5. LeetCode Contains Duplicate
  6. ANDROID L日志系统——JAVAAPI与LIBLOG
  7. CCF认证历年试题解【网上跟帖,请不要使用称呼】
  8. 判断java中String、自定义对象、集合为空的方法
  9. python时间处理模块有哪些_Python模块之时间处理
  10. 常用Oracle分析函数详解
  11. MXNet的Model API
  12. 在Python当中如何打印输出当前时间(代码)
  13. requests-使用代理proxies
  14. netty4 收不到服务器响应的数据_Netty模拟redis服务器
  15. sqlserver分区表索引
  16. 怎么用php myadmin连接远程MYSQL数据库
  17. 52多项式07——有理系数和整系数多项式、埃森斯坦判别法、整系数多项式的有理根
  18. sql 列转行_转行数据分析师如何开始学习SQL | 工科生三个月转行数据分析学习心得...
  19. SPSS数据分析流程
  20. 动词ing形式做定语的用法总结

热门文章

  1. IronPython系列:Observer Pattern及其实现
  2. Java拾遗:007 - 代理模式与动态代理
  3. RFID能否让实体零售业度过“寒冬”?
  4. Myeclipse启动Tomcat服务器Address already in use: JVM_Bind
  5. ngx.location.capture 只支持相对路径,不能用绝对路径
  6. python给空列表赋值_Python Pandas:如果数据是NaN,则更改为0,否则在数据框中更改为1...
  7. 如何快速排查生产问题
  8. RabbitMQ AMQP MessageConverter 消息转换器 PDF Image Text 文本 图片 PDF json
  9. Redis入门之二6379端口
  10. left join后边跟on...and 和where...and的区别