列表优化
其他参考文档:列表无限滚动时,数据如何预加载,从而达到无缝加载的效果 Demo
一、请求时,列表网络数据的预加载
1、第一页的预加载
提前创建 vm 或者 manager 管理请求数据。 (平常用的数据携带、默认数据等本地数据暂不在此讨论)
2、下一页的预加载/无缝加载
预加载是指在Cell还没有出现在屏幕上时,就提前加载它所需的数据和资源。这可以减少Cell出现时的加载时间,提升用户体验。
举例:在用户阅读了最新页码数据的70%(方式1:按视图长度contentSize:UIScrollView所有内容的尺⼨;方式2:按数据长度)时(根据实际情况调节),提前进行下一页数据的加载。这样用户可以省去本来在阅读完已加载的时候需要做一次上拉加载等待数据的过程。
可以看到第一页阈值是70%,即代表进入后即使没滑动也会自动加载第二页。假设每一页都是10条数据,则第一页的阈值是第7条,第二页是第(10+7)条,依次类推…得阈值为 newThreshold = (curPageIndex+0.7)/(curPageIndex+1.0)
图片来源:列表的预加载.graffle
代码实现(方式1:按视图长度contentSize:UIScrollView所有内容的尺⼨):
1 | func scrollViewDidScroll(_ scrollView: UIScrollView) { |
参考文章:预加载与智能预加载(iOS)
其他参考文章:
- Flutter flutter_pulltorefresh 的 footerTriggerDistance 表示当用户滚动到列表底部后,还需要继续滚动的距离,以达到触发加载更多的条件?
二、请求后,数据渲染时的按需加载
滑动时,按需加载:UITableView禁止或者减速滑动结束的时候,进行异步加载图片,快滑动过程中,只加载目标范围内的Cell。
问:从第1个cell滑动到第100个cell。请问在快速滑动情况下如果在tableView(_:cellForRowAt:) 中打印indexPath,能够打印到1到100的indexPath吗
答:在快速滑动 UITableView 从第一个单元格到第100个单元格时,tableView(_:cellForRowAt:) 方法可能会被多次调用,但并不意味着它会为每个索引路径(从0到99)都打印出对应的值。如果用户滑动得非常快,UITableView 为了保持流畅的滚动性能,可能会跳过一些单元格的 tableView(_:cellForRowAt:) 调用,尤其是那些在屏幕外的单元格。
1 | class TableViewController: UITableViewController { |
写法二:不太推荐,性质一样,不过写法有点别扭,相当于cell内容的处理位置变了。
1 | func scrollViewDidScroll(_ scrollView: UIScrollView) { |
优化加强:如果滚动方向改变,快速下滑后又上滑,取消可见区域下面的部分(可见区域上面的部分)。类似于 PrefetchDataSource 的 prefetchRowsAtIndexPaths 和 cancelPrefetchingForRowsAtIndexPaths
延伸
1 | var shouldLoadImages: Bool = true // 假设有一个标记来记录是否应该加载图片 |
其他参考文档:
- iOS10 - PrefetchDataSource 详解 UITableViewDataSourcePrefetching 实现预加载、取消预加载功能
- iOS 10 中的prefetchingEnabled属性,挺有意思的
三、渲染时候的优化
1、ASDK
Texture 拥有自己的一套成熟布局方案,虽然学习成本略高,但至少比原生的 AutoLayout 写起来舒服,重点是性能远好于 AutoLayout。
,ASDK(Texture)主要处理的是 CALayer 而不是 UIView,其核心机制是基于 CALayer 进行异步绘制和异步布局,减少 UIView 相关的 CPU 开销,提高 UI 性能。
参考文档:
- 1、Texture介绍——保持最复杂的用户界面的流畅和响应 最初是为了使Facebook的页面尽可能的流畅
- 2、Texture 布局示例篇
四、UITableView的性能优化
参考资料:UITableView性能优化,超实用
①Cell重用:避免频繁创建和销毁,提高滚动流畅度。
1
2
3
4
5
6 // 返回Cell的代理方法会调用很多次,为防止重复创建,我们使用static 保证只创建一次reuseID,提高性能
static NSString *reuseID = “reuseCellID”;
// 从缓存池中取相应identifier的Cell并更新数据。
// 如果没有,才开始alloc新的Cell,并用identifier标识Cell。每个Cell都会注册一个identifier(重用标识符)放入缓存池,当需要调用的时候就直接从缓存池里找对应的id,当不需要时就放入缓存池等待调用。(移出屏幕的Cell才会放入缓存池中,并不会被release)
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseID];附:比较
1
2
3
4
5
6
7
8
9
10
11
12
13 缓存池获取可重用Cell两个方法的区别
-(nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
// 方法1:这个方法会查询可重用Cell,如果注册了原型Cell,能够查询到,否则,返回nil;而且需要判断if(cell == nil),才会创建Cell,不推荐
-(__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);
// 方法2:使用这个方法之前,必须通过xib(storyboard)或是Class(纯代码)注册可重用Cell,而且这个方法一定会返回一个Cell
// 附:方法2需要的注册Cell的方法
- (void)registerNib:(nullable UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);
- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
// 好处:如果缓冲区 Cell 不存在,会使用原型 Cell 实例化一个新的 Cell,不需要再判断,同时代码结构更清晰。②定义一种(尽量少)类型的Cell及善用hidden隐藏(显示)subviews。即可以初始化时就添加,然后通过hide来控制是否显示。(比如朋友圈),而不要用addView给Cell动态添加View,
③提前计算并缓存Cell的高度;(Model去缓存,或者使用SDAutoLayout工具)
④网络数据的异步加载(如cell中的图片加载),不要阻塞主线程;
⑤滑动时,按需加载,常见于大量图片时候。即当UITableView静止或者减速滑动结束之后才去进行异步加载图片。
⑥渲染优化:减少subviews的个数和层级;对于不透明的View,设置opaque为YES;阴影绘制及性能优化。更新时候:使用局部更新,如果只是更新某组的话,使用reloadSection进行局部更新
1、Cell的重用
基于Cell的重用,真正运行时铺满屏幕所需的Cell数量大致是固定的,设为N个。所以
①如果如果只有一种Cell,那就是只有N个Cell的实例;
②但是如果有M种Cell,那么运行时最多可能会是“M x N = MN”个Cell的实例;
虽然可能并不会占用太多内存,但是能少点不是更好吗。
四、列表加载图片的优化
1、缩略图的使用
图片划分两个地址.一个地址获取缩略图,一个地址获取原图>> 这样你就可以在TableViewCell使用缩略图(展示用),点击图片查看(使用原图). 这样就大大减少了内存的使用.
2、UITableView优化
更轻量的 View Controllers 把 Data Source 和其他 Protocols 分离出来
各页面的预加载
以上内容为同一页面内的预加载处理。那如果是页面间的呢?
在iOS中,使用UITabBarController作为应用程序的主视图控制器时,通常会有几个子视图控制器与之关联。
问:那么这些子视图控制器的初始化和内容渲染分别是在什么时候?或者说刚启动app时候让app在默认的第一个tab,此时第二tab的视图控制器加载了哪些方法,其其他方法又是什么时候触发的。
答:①第二个tab的初始化即init ,在UITabBarController被初始化时,并被设置为窗口(UIWindow)的根视图控制器之后执行。附其他tab的init也一样。
②第二个tab的viewDidload,在 UITabBarController 切换到该tab 时候才会调用。
以上验证代码可在 https://github.com/dvlproad/001-UIKit-CQDemo-iOS 中验证
问1:UITabBarController下,如何预加载指定的视图控制器?
答:数据通过 vm 或者 manager 提前在UITabBarController初始化时进行获取。
问2:UITabBarController下的子视图控制器中如果还有多tab(在顶部),则又如何进行预加载。
答:同1一样。数据通过 vm 或者 manager 提前在该子视图控制器初始化时进行获取。
ListView.builder会按需构建列表元素,也就是只有那些可见得元素才会调用itemBuilder 构建元素
