本文主要从这里个方面讲解UITableView的相关问题:
1,关于UI的数据源绑定问题
2,关于UI的事件传递和事件响应原理
3,关于UI的图像显示原理
4,关于UI的卡顿和掉帧的原因
5,关于UI的绘制原理和异步绘制的实现
6,关于UI的离屏渲染的相关问题
1,UI的数据源绑定
2,UI的事件传递和事件响应原理
2.1,UI的事件响应原理
由此图可以看出,UI的事件的传递流程:
1,UILable,UITextField,UIButton的事件会传递到控件当前的所在的UIView
2,从当前的UIView会找他的父类superView,然后找到UIView所在的UIViewController
3,如果UIView没找到UIViewController,就会传递到UIWindow
4,最终UIViewController或者UIView都会传递到UIWindow,再到UIApplication
5,最后事件被传递到UIApplicationDelegate的方法进行使用
这就是整个事件的传递流程实现,知道了事件传递流程,我们就可以在各个阶段对事件进行处理。
2.2,UI的事件传递
UI的事件响应原理,首先我们需要了解这两个方法的实现:
1,- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event; //测试点击的区域
2,- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; //判断点击是否在区域中
屏幕点击事件的具体流程:
点击屏幕触发点击事件 ---> 触发系统的UIApplication ---> 通过UIApplication找到UIWindow ---> 调用hitTest: withEvent:---> 通过pointInside: withEvent:判断是否在点击区域 ---> 最后找到subviews
通过上图,我们来分析事件的传递:
1,判断当前视图的(alpha > 0.01,ishidden == no, isuserinteractionEnble == true ),可以获取点击事件
2,通过pointInside:withEvent:方法判断是否在点击区域内,如果为Yes执行3
3,通过倒叙遍历视图的subviews,如果有subviews,执行4
4,通过hitTest:withEvent:方法查找superview,找到就传递给superview,实现整个的事件传递。
3,UI的图像显示原理
通过上面两幅图我们可以看出,UI的图像显示需要CPU和GPU共同显示完成。
1,CPU主要负责:对象创建,对象调整,布局计算,文本渲染,图片解码,图像绘制等工作。
2,GPU主要负责:纹理渲染,视图混合,图像形成等工作。
视图UI显示时,UIView会连接到CALayer,CALayer会实现了CADisplayNode方法进行绘制,具体实现见上图详细分析。
4,UI的卡顿和掉帧
1,屏幕显示的时候,是由垂直同步信号(VSync)和水平同步信号(HSync)组成,先发出垂直同步信号,再一行一行的发出水平同步信号进行显示
2,按照每秒60FPS的刷帧率,每隔16.7ms就会有一次VSync信号,如果每一次CPU和CPU工作的总耗时超过16.7ms,就会造成卡顿掉帧
针对UI掉帧和卡顿的优化主要针对CPU和GPU进行优化
CPU:
- 尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView
- 不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改
- 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
- Autolayout会比直接设置frame消耗更多的CPU资源
- 图片的size最好刚好跟UIImageView的size保持一致
- 控制一下线程的最大并发数量
- 尽量把耗时的操作放到子线程
- 减少文本处理(尺寸计算、绘制)
- 减少图片处理(解码、绘制)
GPU:
- GPU能处理的最大纹理尺寸是4096x4096,一旦超过这个尺寸,就会占用- CPU资源进行处理,所以纹理尽量不要超过这个尺寸
- 尽量减少视图数量和层次
- 减少透明的视图(alpha<1),不透明的就设置opaque为YES
- 尽量避免出现离屏渲染
参考链接:https://www.jianshu.com/p/a96b7dd7d3ad
5,UI绘制原理和异步绘制
5.1 UI的绘制原理
我们通过两幅图来了解UI的绘制原理实现
1,UIView进行绘制时,UIView会先调用setNeedsDisplay方法
2,然后通过view.layer调用layer的setNeedsDisplay方法
3,传递到CALayer,调用CALayer的display方法
4,CALayer会通过代理回调,去实现displayLayer的方法,如果有就进入异步绘制,否则进入系统绘制流程。否则进入5.2UI的异步绘制
系统绘制流程请看下图
5,CALayer会通过创建backing store的CGContextRef,然后判断layer的delegate
6,如果delegate存在就会调用[layer.delegate drawLayer:inContext:]然后调用UIView的drawRect方法处理视图,传递到GPU
7,如果delegate不存在,就会直接调用CALayer的drawInContext方法,直接处理视图,传递到GPU。
5.2 UI的异步绘制
我们来看一下UI的异步绘制实现流程
异步绘制主要实现在上图的3.2当中
1,在子线程中,CA Layer的代理负责生成对应的bitMap
2,通过CoreGraphic的API调用实现,把bitMap作为layer的comntent属性值实现
3,最后回到CA Layer的content中。
6,UI的离屏渲染
先谈谈什么会触发离屏渲染?
1,图像设置圆角和maskToBounds一起使用时
2,UI图层设置蒙板
3,UI图层设置阴影
4,GPU实现光栅化操作
避免离屏渲染:优化圆角,少用阴影,渐变
参考:https://www.jianshu.com/p/52c72f18e142
7,面试问题
1,UITableView的滚动更加流畅的思路和方法有哪些?
答:1,提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;
2,滑动时按需加载,防止卡顿,这个我也认为是很有必要做的性能优化,配合SDWebImage 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口。
3,异步绘制cell的实现
4, 缓存一切可以缓存的,这个在开发的时候,往往是性能优化最多的方向,缓存view的实现。
5,避免同步现在,使用异步下载网络图片
6,减少渲染的view的层级和数量。