我们知道 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. CoreData 从入门到精通 (一) 数据模型 + CoreData 栈的创建

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

  2. Python运算符,python入门到精通[五]

    运算符用于执行程序代码运算,会针对一个以上操作数项目来进行运算.例如:2+3,其操作数是2和3,而运算符则是“+”.在计算器语言中运算符大致可以分为5种类型:算术运算符.连接运算符.关系运算符.赋值运 ...

  3. MyBatis从入门到精通(五):MyBatis 注解方式的基本用法

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. @Select 注解 1.1 使 ...

  4. CoreData 从入门到精通(六)模型版本和数据迁移

    前面几篇文章中讲的所有内容,都是在同一个模型版本上进行操作的.但在真实开发中,基本上不会一直停留在一个版本上,因为需求是不断变化的,说不定什么时候就需要往模型里添加新的字段,添加新的模型,甚至是大规模 ...

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

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

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

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

  7. CoreData 从入门到精通(二) 数据的增删改查

    在上篇博客中,讲了数据模型和 CoreData 栈的创建,那下一步就是对数据的操作了.和数据库一样,CoreData 里的操作也无非是增删改查.下面我们将逐步讲解在 CoreData 中进行增删改查的 ...

  8. iOS开发-UI 从入门到精通(五)

    近日在做项目的时候,为了快捷适配屏幕采用了Storyboard,添加约束以后运行后发现一个问题(下面将以普通案例展示该问题);在4.7 甚至更大的屏幕下是没有问题的,如下图(4.7屏幕): 但是放到更 ...

  9. Atom编辑器入门到精通(五) Git支持

    版本控制对于开发来说非常重要,Atom当然也提供了很好的支持,本文将介绍如何在Atom中集成使用Git和GitHub 恢复文件 当你修改了某个文件,然后发现改得不满意,希望恢复文件到最后一次提交的状态 ...

随机推荐

  1. 51nod-1462: 树据结构

    [传送门:51nod-1462] 简要题意: 给出一棵n个点的树,每个点有两个权值v,t 有Q个操作,有两种操作: 1.将x到根上的路径上的点的v值都加上d 2.将x到根上的路径上的点的t值都加上每个 ...

  2. jetty服务器数据源配置JNDI-Oracle,MySQL,SQLServer,DB2等 (转)

    下载jetty 下载jetty服务器(8.1.0.RC2),解压到任意目录下 http://dist.codehaus.org/jetty/jetty-hightide-8.1.0/jetty-hig ...

  3. Python笔记(五)

    # -*- coding:utf-8 -*- # 函数 # python中定义函数的规则如下:以def开头,接函数名称和(),传入的参数和变量放在圆括号中间,函数以:起始,并且缩进,return选择性 ...

  4. BZOJ 1500 splay终结版...

    GSS系列有一丝丝像- 只不过那个是线段树 这个是splay 翻转 插入 删除啥的就是普通的splay 合在一起了而已 //By SiriusRen #include <cstdio> # ...

  5. Date.getTime() 结果为 NaN

    yyyy-MM-dd 格式的时间,部分浏览器环境下转换为 Date 对象后调用 getTime() 方法的结果为 NaN. 需要将 - 替换为 / var dateStr = '2019-01-01' ...

  6. 【摘录】JAVA内存管理-评估垃圾收集性能的工具

    第七章 评估垃圾收集性能的工具 各种各样的诊断和监视工具可以用来评估垃圾收集性能.本章简要概述他们中的几个.可以通过第九章中的“Tools and Troubleshooting”链接获得更多的信息. ...

  7. 快速新建一个纯净的java pom项目 project

    前期的java环境安装就不再阐述了使用步骤java -jar project-creator-0.1.jar projectName [war] 1> 比如你要创建一个项目名字叫 smile-o ...

  8. DDD中 与Dto搭配的AutoMapper插件,摘自《NET企业级应用架构设计》

    AutoMapper插件 实现了 DTO与Model的互相映射.

  9. Python内置数据结构之字典dict

    1. 字典 字典是Python中唯一的内置映射类型,其中的值不按顺序排列,而是存储在键下.键可能是数(整数索引).字符串或元组.字典(日常生活中的字典和Python字典)旨在让你能够轻松地找到特定的单 ...

  10. hdu1542 矩形面积并(线段树+离散化+扫描线)

    题意: 给你n个矩形,输入每个矩形的左上角坐标和右下角坐标. 然后求矩形的总面积.(矩形可能相交). 题解: 前言: 先说说做这道题的感受: 刚看到这道题顿时就懵逼了,几何 烂的渣渣.后来从网上搜题解 ...