iOS UITableView优化
一、Cell 复用
在可见的页面会重复绘制页面,每次刷新显示都会去创建新的 Cell,非常耗费性能。
解决方案:创建一个静态变量 reuseID,防止重复创建(提高性能),使用系统的缓存池功能。
static NSString * CELL_RUID = @"CELL"; // 调用次数太多,static 保证只创建一次 reuseID,提高性能
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 缓存池中取已经创建的 cell
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CELL_RUID
forIndexPath:indexPath];
return cell;
}
通过 identifier 标识不同类型的 cell,缓存池中只会保存已经被移出屏幕的不同类型的 cell。
- (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier; // Used by the delegate to acquire an already allocated cell, in lieu of allocating a new one.
- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0); // newer dequeue method guarantees a cell is returned and resized properly, assuming identifier is registered
复用 Cell 时 不会调用 awakeFromNib。
- 获取方法的区别
dequeueReusableCellWithIdentifier:forIndexPath 如果没有注册复用 identifier,执行这句时会崩溃,提示:
reason: 'unable to dequeue a cell with identifier CELL - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
dequeueReusableCellWithIdentifier 如果没有注册复用 identifier,语句返回 nil,继续执行会崩溃。提示:
failed to obtain a cell from its dataSource
判断 nil 后可以自己创建 cell。
{
MyCell * cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
if (cell == nil) {
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
}
}
- 为什么需要 forIndexPath:
因为在返回 cell 之前,会调用委托 tableView:heightForRowAtIndexPath:来确定 cell 尺寸(如果已经定义该函数)。
我们经常在 tableView:cellForRowAtIndexPath: 中为每一个 cell 绑定数据,实际上在调用 cellForRowAtIndexPath: 的时候 cell 还没有被显示出来,为了提高效率我们应该把数据绑定的操作放在 cell 显示出来后再执行,可以在 tableView:willDisplayCell:forRowAtIndexPath: 方法中绑定数据。
注意 willDisplayCell 中 cell 在 tableview 展示之前就会调用,此时 cell 实例已经生成,所以不能更改 cell 的结构,只能是改动 cell 上的 UI 的一些属性,如 label 的内容、控件的隐藏等。
二、定义一种(尽量少)类型的 Cell 及善用 hidden 隐藏(显示)subviews
分析 Cell 结构,尽可能的将相同内容的抽取到一种样式 Cell 中。UITableView 真正创建出的 Cell 可能只比屏幕显示的多一点。虽然 Cell 的"体积"可能会大点,但是因为 Cell 的数量不会很多,完全可以接受的。
好处:
①、减少代码量,减少 Nib 文件的数量,在一个 Nib 文件定义 Cell,容易修改、维护;(多个 Cell 不是更容易维护?)
②、基于复用机制,真正运行时铺满屏幕所需的 Cell 数量大致是固定的,设为 N 个。如果只有一种 cell,那就是只有 N + c 个 cell 的实例;但是如果有 M 种 cell,那么运行时最多可能会是 M * (N + c) 个 cell 的实例,虽然这可能并不会占用太多内存,但能少一些更好。
既然只定义一种 Cell,那么需要把所有不同类型的 view 都定义好,放在 Cell 里面,通过 hidden 属性控制,来显示不同类型的内容。毕竟,在用户快速滑动中,只是单纯的显示/隐藏 subview 比实时创建要快得多。
尽量少用 [cell addSubview:] 动态添加 View,可以初始化时就添加,然后通过 hidden 属性来控制。
三、提前计算并缓存 Cell 的高度
3.1 固定高度的 cell
self.tableView.rowHeight = 88;
直接采用上面方式给定高度,不需要实现 tableView:heightForRowAtIndexPath: 以节省不必要的计算和开销。
3.2 动态高度的 cell
实现代理方法后,上面的 rowHeight 属性的设置将会变成无效。
tableView:estimatedHeightForRowAtIndexPath: -> tableView:heightForRowAtIndexPath: 获取每个 Cell 即将显示的高度,从而确定表格视图的布局,实际是要获取滚动视图的 contentSize,然后调用 tableView:cellForRowAtIndexPath:,获取每个 Cell,进行赋值。如果有很多个 Cell 要显示,那么方法会执行很多次。
解决方案:在 Model(Entity)中计算并保存 Cell 的高度。其实 Model 中保存 UI 的参数是很奇怪的,最好放在 MVVM 模式的 ViewModel(视图模型)中,让 Model(数据模型)只负责处理数据。
@interface Model : NSObject
@property (nonatomic, assign) CGFloat cellHeight; // Cell 高度
/**
* @brief 计算高度
*/
- (void)calculateCellHeight;
@end
在 tableView:heightForRowAtIndexPath: 中尽量不使用 cellForRowAtIndexPath: 方法来获取 cell,如果你需要用到它,只用一次然后缓存结果。
还可以继续进行优化,提前创建真正显示的、需要加工的数据并缓存。如:接口返回 NSString 而展示 NSAttributeString。
四、异步绘制(自定义 Cell 绘制)
遇到比较复杂的界面时(复杂点的图文混排),上面缓存行高的方式可能就不能满足要求了。详细整理:UITableView 优化技巧
/**
* @brief cell 添加 draw 方法
*/
- (void)draw
{
// 异步绘制
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
});
}
/**
* @brief 重写 drawRect: 方法
*/
- (void)drawRect:(CGRect)rect
{
// 不需要用 GCD 异步线程,因为 drawRect: 本来就是异步绘制的。
}
绘制的各个信息都是根据之前算好的布局进行绘制的。这里是需要异步绘制。
五、滑动时,按需加载
自定义 Cell 的种类千奇百怪,但它本来就是用来显示数据的,差不多 100% 带有图片,这个时候就要考虑,下滑的过程中可能会有点卡顿,尤其网络不好的时候,异步加载图片是个程序员都会想到,但是如果给每个循环对象都加上异步加载,开启的线程太多,一样会卡顿。这个时候利用 UIScrollViewDelegate 两个代理方法就能很好地解决这个问题。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (needLoadArr.count > 0 && [needLoadArr indexOfObject:indexPath] == NSNotFound) {
[cell clear]; // 清掉内容
}
return cell;
}
// 按需加载 - 如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定 3 行加载。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
NSIndexPath * ip = [self.tableView indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];
NSIndexPath * cip = [[self.tableView indexPathsForVisibleRows] firstObject];
NSInteger skipCount = 8;
// -8 < 当前位置 - 目标位置 < 8
if (labs(cip.row - ip.row) > skipCount) {
// 目标区域的 cell 的 indexPaths
NSArray * temp = [self.tableView indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, self.tableView.frame.size.width, self.tableView.frame.size.height)];
NSMutableArray * arr = [NSMutableArray arrayWithArray:temp];
if (velocity.y < 0) {
NSIndexPath * indexPath = [temp lastObject];
if (indexPath.row + 33) {
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row - 3 inSection:0]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row - 2 inSection:0]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row - 1 inSection:0]];
}
}
[needLoadArr addObjectsFromArray:arr];
}
}
思想:识别 UITableView 拖拽即将结束的时候,进行异步加载图片,快滑动过程中,只加载目标范围内的 Cell,这样按需加载,极大的提高流畅度。而 SDWebImage 可以实现异步加载,与这条性能配合就完美了,尤其是大量图片展示的时候。而且也不用担心图片缓存会造成内存警告的问题。
六、缓存 View
当 Cell 中的部分 View 是非常独立且不便于重用的,"体积"非常小,在内存可控的前提下,完全可以将这些 view 缓存起来。
七、尽量显示“大小刚好合适的”图片资源
避免大量的图片缩放、颜色渐变等。
八、避免同步的从网络、文件获取数据
Cell 内实现的内容来自 web,使用异步加载,缓存请求结果。
九、渲染
减少 subviews 的个数和层级
子控件的层级越深,渲染到屏幕上所需要的计算量就越大;如多用 drawRect 绘制元素,替代用 view 显示。
少用 subviews 的透明图层
渲染最耗时的操作之一就是混合(blending)了。对于不透明的 View,设置 opaque = YES,这样在绘制该 View 时,避免 GPU 对 View 覆盖的其他内容也进行绘制。
背景色不要使用 clearColor
避免 CALayer 特效(shadowPath)
给 Cell 中 View 加阴影会引起性能问题,如下面代码会导致滚动时有明显的卡顿:
view.layer.shadowColor = color.CGColor;
view.layer.shadowOffset = offset;
view.layer.shadowOpacity = 1;
view.layer.shadowRadius = radius;
当有图像时,预渲染图像,在 bitmap context 先将其画一遍,导出成 UIImage 对象,然后再绘制到屏幕,这会大大提高渲染速度。具体内容可以自行查找“利用预渲染加速显示 iOS 图像”相关资料。
十、总结
UITableView 的优化主要从四个方面入手:
- 提前计算并缓存好高度(布局),因为 tableView:heightForRowAtIndexPath: 是调用最频繁的方法;
- 滑动时按需加载,防止卡顿。这个在大量图片展示,网络加载的时候很管用,配合 SDWebImage;
- 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
- 缓存一切可以缓存的,这个在开发的时候,往往是性能优化最多的方向。
大概需要关注的:
- cell 复用
- cell 高度的计算
- 渲染(混合问题)
- 减少视图的数目(重写 drawRect:)
- 减少多余的绘制操作
- 不要给 cell 动态添加 subView
- 异步化 UI,不要阻塞主线程
- 滑动时按需加载对应的内容
十一、资料
图片加载优化官方 Demo:LazyTableImages
优化UITableViewCell高度计算的那些事
UITableView+FDTemplateLayoutCell
iOS UITableView优化的更多相关文章
- 【腾讯Bugly干货分享】微信读书iOS性能优化
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/578c93ca9644bd524bfcabe8 “8小时内拼工作,8小时外拼成长 ...
- iOS 滑动性能优化
iOS 滑动性能优化 目录 一. 减少图层的Blend操作 1. UIView的背景色避免使用clearColor 2. 控件贴图避免使用带alpha的图片 3. UIImageView 使用时避免半 ...
- UITableView优化那点事
forkingdog关于UITableView优化的框架其实已经能够应用在一般的场景,且有蛮多的知识点供我们借鉴,借此站在巨人的肩膀上来分析一把. 至于UITableView的瓶颈在哪里,我相信网上随 ...
- [转] 详细整理:UITableView优化技巧
原文:http://www.cocoachina.com/ios/20150602/11968.html 最近在微博上看到一个很好的开源项目VVeboTableViewDemo,是关于如何优化 ...
- IOS 性能优化的建议和技巧
IOS 性能优化的建议和技巧 本文来自iOS Tutorial Team 的 Marcelo Fabri,他是Movile的一名 iOS 程序员.这是他的个人网站:http://www.marcelo ...
- uitableview 优化
1. http://www.cocoachina.com/ios/20150602/11968.html 最近在微博上看到一个很好的开源项目VVeboTableViewDemo,是关于如何优化UITa ...
- UITableView 优化总结
最近在微博上看到一个很好的开源项目VVeboTableViewDemo,是关于如何优化UITableView的.加上正好最近也在优化项目中的类似朋友圈功能这块,思考了很多关于UITableView的优 ...
- iOS tableView优化
iOS: Autolayout和UITableViewCell的动态高度 http://www.mgenware.com/blog/?p=507 优化UITableViewCell高度计算的那些事 h ...
- iOS UITableView Tips(2)
#TableView Tips(2) (本来想一章就结束TableView Tips,但是发现自己还是太天真了~too young,too simple) ##架构上的优化 在Tips(1)中指出了一 ...
随机推荐
- 聊一聊MyBatis 和 SQL 注入间的恩恩怨怨
整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 更多优选 一口气说出 9种 分布式ID生成方式,面试官有点懵了 ...
- JVM性能优化
java应用程序是应用在JVM上的,你们对JVM又有多少了解呢?JVM将内存分为三部分:NEW(年轻代).Tenured(年老代).Perm(永久代). (1)年轻代:用来存放java分配的新对象. ...
- qt creator源码全方面分析(3-2)
目录 qtcreator.pri 判断重复包含 定义版本信息 VERSION 定义IDE名称 启用C++14 CONFIG 自定义函数 Replace Functions Test Functions ...
- Azure Devops/TFS测试管理(下)
紧接着 上篇 经过上篇折腾,我们已经有了: ①手工测试的流程规范 ②测试用例的管理 对于开发出身的我,我觉得一个项目上线流程应该主要瓶颈只能是开发本身,因为我认为最复杂过程应该就是开发,而肯定不能是测 ...
- 一步步去阅读koa源码,整体架构分析
阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想.大到架构设计,小到可取的命名风格,还有设计模式.实现某类功能使用到的数据结构和算法等等. 使用koa 其实某个框架阅读源码的时候,首 ...
- IDEA 配置自定义Apache与PHP环境
1. PHP环境 1.1 插件的安装 1.2 关于php环境的配置 2.关于apache的配置 至此,已经配置成功啦,愉快的学习吧!
- python正则表达式之re模块使用
python第一个正则表达式 https://www.imooc.com/learn/550 r'imooc' Pattern Match result In [2]: import re In [ ...
- 关于独立部署web项目到tomcat服务器详情
步骤: 1.设置端口号:找到所解压的tomcat的目录下中的conf文件夹,再用editPlus打开conf文件夹中的server.xml文件,tomcat初始端口为8005,8080,8009,如果 ...
- 前端AES加密解密
最开始使用的aes-js的npm包,后来发现npm上面那个包只能加密16个长度的字节,非16个长度的字符串就会报错,后来使用的是crypto-js, AES总共有四种加密方式,我们使用的CBC方式: ...
- IoT设备实践丨如果你也在树莓派上部署了k3s,你也许需要这篇文章
前 言 树莓派是一种广泛流行的开发板,随着物联网的深入发展,树莓派大有成为IoT终端设备标准之趋势.在支持客户在IoT场景中落地k3s时,k3s在树莓派上的部署问题也就出现了.本文记录了一些其中的关键 ...