iOS中UI控件内容显示流程
UIKit界面组成
iOS中组成页面的各个元素基本来自UIKit,我们可以修改布局或自定义绘制来修改UIKit元素的默认展示。
UIView的页面显示内容有CALayer负责,事件的接收与响应由UIView自己负责。
为什么需要有这样的分工呢,原因是因为Mac上和iPhone上的事件存在很大的区别,iPhone 是屏幕触摸事件,Mac上是鼠标,键盘等事件,但是显示上却是高度一致的,因此把显示部分单独封装成CALayer而存在来。
UIView默认是CALayer的CALayerDelegate,它负责创建并管理它的图层,以确保当子视图在层级关系中添加或者被移除的时候,它们关联的图层也同样对应在层级关系树当中有相同的操作。
每个View被创建的时候都会自动创建一个CALayer,同时还可以在后续的操作中添加多个layer。
CALayer有个id类型的contents属性,它指向内存中的一个成为backing storage的存储空间。往contents上赋值的时候就会将UIView的显示内容存储到这个backing storage中,
这里个id类型是一个兼容的写法,它在iOS上时CGImageRef类型,在Mac OS上是NSImage类型。
如何将显示的内容绘制到CALayer上
在创建流程中可分成两大分支:
1.通过CALayer的Delegate绘制
简单理解为通过实现UIView的代理方法displayLayer:或者重写CALayer的display方法,手动给layer.contents赋值,将内容绘制到CALayer 默认的backing store上。
在我们调用[UIView setNeedsDisplay]的时候,会触发[view.layer setNeedsDisplay],紧接着调用[view.layer display] 在这个方法中会判断layer.delegate 是否实现了displaylayer如果有则将layer传递出去,
然后在UIView的displayLayer:(CALayer *)layer方法中对contents进行赋值。注意:UIView默认为layer.delegate。
具体案例有:SDAnimatedImageView的代理实现
- (void)displayLayer:(CALayer *)layer {
UIImage *currentFrame = self.currentFrame;
if (currentFrame) {
layer.contentsScale = currentFrame.scale;
layer.contents = (__bridge id)currentFrame.CGImage;
}
}
另一种实现方法是在CALayer中复写layer的display方法,在其中对contents进行赋值
具体案例有:YYTextAsyncLayer的重写实现
- (void)display {
super.contents = super.contents;
[self _displayAsync:_displaysAsynchronously];
}
2.使用系统内部绘制
系统开始的时候会创建一个新的backing store,然后开始走drawInContext,这时候会先看layer.delegate是否实现了drawRect
如果有则用drawRect,
否则调用drawLayer:inContext:
并将管理新建backing store的context传递出来。
提交图层树到Render Server
UIView的显示内容创建好之后,后面就是准备渲染了。
在一个界面从开始到提交到Render Server前一共可以分成三个步骤:
Layout
Prepare && Display
Commit
Layout
一个控件在添加到界面上时,会自动触发布局,从而确定整个层级数中每个控件的frame。
Prepare && Display
这部分会涉及到图片的解码,文本绘制,或者通过CALayer暴露出来的CGContextRef在backing store中进行绘制。
图片解码一般发生在Prepare阶段。
存储在backing store的 bitmap后续就会被打包送到Render Server中。
Commit
当RunLoop即将进入休眠期间或者即将退出的时候,会通过已经注册的通知回调执行_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv函数,
在这个函数会递归将待处理的图层进行打包压缩,并通过IPC方式发送到Render Server。
这时候的Core Animation会创建一个OpenGL ES纹理并将backing store中的位图上传到对应的纹理中。
在将图层树发送到GPU之前Core Animation做的处理工作
Render Server在拿到压缩后的数据的时候,首先对这些数据进行解压,从而拿到图层树,然后根据图层树的层次结构,每个层的alpha值opeue值,RGBA值、以及图层的frame值等对被遮挡的图层进行过滤,删除无需渲染的图层,最终得到渲染树,渲染树就是指将图层树对应每个图层的信息,比如顶点坐标、顶点颜色这些信息,抽离出来,形成的树状结构。GPU收到的原始处理数据就是这课渲染树。
然后将渲染树发送到GPU,GPU开启真正的渲染流程。
GPU的渲染流程
往细得分可以分成六个阶段
顶点着色器(Vertex Shader):
在Render Server 拿到顶点数据并输入到渲染管线的时候,顶点着色器会对每个顶点数据进行一次运算,每个顶点都对应一组顶点数组,这些数组可以用于存储:顶点坐标,RGBA颜色,辅助颜色,纹理坐标以及多边形边界标志等。
图元装配(Shape Assembly):
图元装配的过程就是将顶点连接起来,形成一个个所支持的图元元素
几何着色器(Geometry Shader):
把图元装配后的产物,图元形式的一系列顶点的集合作为输入,来产生新顶点构造出新的图元来生成其他形状
光栅化(Rasterization):
光栅化会把图元映射为最终屏幕上相应的像素,生成供片段着色器使用的片段,OpenGL中的一个片段是OpenGL渲染一个像素所需的所有数据,它包含位置,颜色,纹理坐标等信息。
裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
片段着色器(Fragment Shader):
片段着色器的主要目的是计算一个像素的最终颜色,包括光照、阴影、光的颜色等等,这些数据可以被用来计算最终像素的颜色。
根据顶点着色器输出的顶点纹理坐标对纹理贴图进行采样,以计算该片段的颜色值。从而调整成各种各样不同的效果图。
测试与混合(Tests and Blending):
检测片段的对应的深度值,用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值并对物体进行混合(Blend)。
屏幕显示器显示原理
显示器和GPU的关系是生产消费者关系,GPU生成要显示的图像数据放到帧缓冲区,显示器从帧缓冲区读取出来,在屏幕上展示。
显示器的展示原理是使用电子枪从左上角到右下角逐屏扫描的。扫描枪在扫描过程中严格根据扫描枪信号进行扫描。
当水平信号HSync来时,扫描枪从左到右扫描一行,然后移动到下一行等待,当下个HSync到来时,重复上一个操作。
当一屏幕扫描完成,扫描枪回到左上角,等待VSync下一个垂直信号的到来。
显示器和GPU之间使用了双缓存机制,在显示器显示某帧数据的时候,GPU可以往另一个缓存中提交渲染好的数据,在VSync信号到来的时候,视频控制器切换到另一个缓存用于显示,如果在规定时间1/60s内没有完成往另一个缓存中写入要展示的数据,这时候视频控制器就不会将缓存切换到未完成的帧,而是继续显示当前的内容。这就给人们带来视觉上的卡顿。

离屏渲染
屏幕渲染流程有2种方式:正常渲染流程,离屏渲染流程
正常渲染流程
CPU通过布局计算,文本绘制,图片解码,将得到的渲染树通过Core Animation提交给GPU
GPU使用画家算法,根据图层距离屏幕的距离由远到近分别对图层进行纹理映射,顶点着色,光栅化等得到每个图层的像素效果,再通过图层混合,透明计算,深度计算得出可以展示的图像信息放到帧缓存区
视频控制器在每个垂直信号来到时,读取帧缓存区数据在屏幕上展示,然后立即丢弃这帧数据,不做任何保留。这样可以提高渲染性能,不同帧数据各自独立。
离屏渲染流程
当给视图设置圆角,阴影,蒙版这些图层预合成属性时,表示视图内容(包括layer及其所有的sublayers)在其预合成之前是不能在屏幕中绘制的,即:预合成之前不能放到帧缓冲区。
因为帧缓存区的图层数据是用完就丢弃,本地不会记录,所以需要开辟一个离屏缓存区用来存储视图中所有要处理的图层数据,先按画家算法由远到近逐个将图层渲染近缓存区,然后再按画家算法的顺序由远到近的进行画圆角,最后把它们合并叠加,把结果一起放到帧缓存区中。
视频控制器在每个垂直信号来到时,读取帧缓存区数据在屏幕上展示。

触发离屏渲染的方式
1.shouldRasterize(光栅化)
2.masks(遮罩)
3.shadows(阴影)
4.edge antialiasing(抗锯齿)
5.group opacity(不透明)
6.复杂形状设置圆角等
7.渐变
离屏渲染的问题
增加性能消耗,可能导致掉帧,CPU从收到的渲染树到转成bitmap写到帧缓冲区,这一套流水线是源源不断的。而因为要使用离屏渲染,则先要把每个图层的处理结果不断记录在离屏缓冲区,最后还要做进行额外的处理,然后再把处理结果移动到当前帧缓冲区。这是二个流程的切换,中间要记录上下文。这个额外的操作对于
性能消耗比较大,如果不能1/60s完成,可能造成掉帧
离屏渲染要额外开辟一内存进行预合成操作浪费内存。
离屏渲染的优点
保存中间状态:如果一些视图状态不能一次性完成,则可以临时保存中间状态,如圆角,阴影,蒙版。
提升渲染效率:如果一个状态要多次渲染,可以提前渲染完成放到离屏缓冲区,等屏幕展示时直接使用。如开启光栅化
复用离屏渲染结果
shouldRasterize(光栅化)
当设置视图的shouldRasterize = YES,开启光栅化时,系统会将视图离屏渲染得到的结果(如:添加了阴影,遮罩后的结果)保存到位图中缓存起来,这里的位图中的元素和帧缓冲区的像素是一一对应的。
如果视图的 layer 及其 sublayers 都没有发生变化,则在下一帧渲染时直接拿来复用,提供了渲染效率。
光栅化是把GPU的渲染工作从GPU挪到了CPU,并将结果位图做了缓存,等屏幕展示时,直接拿来复用。
这对视图内容复杂,绘制起来麻烦,而又不怎么变化的场景比较适合(它会将整个视图作为一张图片进行保存,等展示时直接拿来复用),而对于经常变化需要重绘的视图如tableViewCell,则返回会增加内存消耗,因为TableViewCell有复用机制,Cell中的内容会经常变化。
光栅化使用建议如下:
1.如果layer不能被复用,则没有必要开启光栅化
2.如果layer不是静态,需要被频繁修改(例如动画过程中),此时开启光栅化反而影响效率
3.离屏渲染缓存内容有时间限制,如果100ms内没有被使用,那么就会丢弃,无法进行复用
4.离屏渲染的缓存空间有限,是屏幕的2.5倍,超过2.5倍屏幕像素大小的话也会失效,无法实现复用
Instruments 监测离屏渲染
1)Color Offscreen-Rendered Yellow,开启后会把那些需要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题。
2)Color Hits Green and Misses Red,如果 shouldRasterize 被设置成YES,对应的渲染结果会被缓存,如果图层是绿色,就表示这些缓存被复用;如果是红色就表示缓存会被重复创建,这就表示该处存在性能问题了。
平衡CPU与GPU
GPU部分:GPU擅长图形处理,这依赖与GPU内部有成千上万的计算单位可以并行运算
CPU部分:
而对于文字(CoreText使用CoreGraphics渲染)和图片(ImageIO)渲染,由于GPU并不擅长做这些工作,不得不先由CPU来处理好以后,再把结果作为texture传给GPU
可以使用CoreGraphics给图片加上圆角,就不需要再另外给图片容器设置cornerRadius了,可以在CPU空闲的时候进行操作
注意:
1.渲染不是CPU的强项,调用CoreGraphics会消耗其相当一部分计算时间,一般来说CPU渲染都在后台线程完成(这也是AsyncDisplayKit的主要思想),然后再回到主线程上,把渲染结果传回CoreAnimation。
2.CPU只适合渲染静态的元素,如文字、图片
3.作为渲染结果的bitmap数据量较大(形式上一般为解码后的UIImage),消耗内存较多,所以应该在使用完及时释放
4.如果使用CPU来做渲染,就没有理由再触发GPU的离屏渲染了
参考文章:
https://zhuanlan.zhihu.com/p/381766140
https://juejin.cn/post/6950920557445513229
https://www.cnblogs.com/mysweetAngleBaby/p/16341632.html
https://tbfungeek.github.io/2019/08/04/iOS-渲染系统工作原理介绍/
- VS2010/MFC编程入门之二十八(常用控件:列表视图控件List Control 上)
前面一节中,鸡啄米讲了图片控件Picture Control,本节为大家详解列表视图控件List Control的使用. 列表视图控件简介 列表视图控件List Control同样比较常见, ...
- MFC编程入门之二十八(常用控件:列表视图控件List Control上)
前面一节中,讲了图片控件Picture Control,本节为大家详解列表视图控件List Control的使用. 列表视图控件简介 列表视图控件List Control同样比较常见,它能够把任何字符 ...
- VS2010-MFC(常用控件:列表视图控件List Control 上)
转自:http://www.jizhuomi.com/software/195.html 列表视图控件简介 列表视图控件List Control同样比较常见,它能够把任何字符串内容以列表的方 ...
- iOS:UITableView表格视图控件
UITableView:表格视图控件,继承滚动视图控件UIScrollView,(类似于UIPickerView选择器,它主要通过设置数据源代理和行为代理实现协议来设置单元格) 对表格的操作主要 ...
- iOS:文本视图控件UITextView的详细使用
文本视图控件:UITextView 介绍:它是一个文本域的编辑视图,可以在该区域上进行编辑(包括删除.剪贴.复制.修改等),它与文本框UITextField的不同之处是:当它里面的每一行内容超出时,可 ...
- iOS:网页视图控件UIWebView的详解
网页视图控件:UIWebView 功能:它是继承于UIView的,是一个内置的浏览器控件,以用来浏览从网络下载下来的网页或者本地上加载下来的文档. 枚举: //网页视图导航类型 typedef NS_ ...
- iOS:UIImageView图像视图控件
UIImageView:图像视图控件: 它是UIView的子类,因此也是视图控件,可以用来显示图像.因为它具有帧动画属性和操作方法,因此可以用来制作动画,其实动画就是很短的时间内,执行显示连续的 ...
- iOS:风火轮活动刷新视图控件UIActivityIndicatorView的详细使用
动态风火轮视图控件:UIActivityIndicatorView 介绍:它是一种类似于风火轮旋转的视图控件,可用作刷新数据时显示加载过程所用,继承自UIView. 类型: typedef N ...
- IOS—UITextFiled控件详解
IOS—UITextFiled控件详解 //初始化textfield并设置位置及大小 UITextField *text = [[UITextField alloc]initWithFrame:CGR ...
- [iOS基础控件 - 5.1] UIScrollView
A.需要掌握 UIScrollView 是一个能够滚动的视图控件,可以用来展示大量内容,如手机的“设置” 1.常见属性 2.常用代理方法 3.缩放 4.UIScrollView和UIPageContr ...
随机推荐
- lec-1-Deep Reinforcement Learning, Decision Making, and Control
What is RL 基于学习的决策的数学形式 从经验中学习决策和控制的方法 Why should we study this now 深度神经网络特征方法 强化学习的提升 计算能力的提升 我们还需要 ...
- Python从0到1丨带你认识图像平滑的三种线性滤波
摘要:常用于消除噪声的图像平滑方法包括三种线性滤波(均值滤波.方框滤波.高斯滤波)和两种非线性滤波(中值滤波.双边滤波),本文将详细讲解三种线性滤波方法. 本文分享自华为云社区<[Python从 ...
- 深入理解 apply()方法
apply(thisArg) apply(thisArg, argsArray) thisArg 在 func 函数运行时使用的 this 值.请注意,this 可能不是该方法看到的实际值:如果这个函 ...
- 【最佳实践】如何设计 RESTful API ?
良好的 API 设计是一个经常被提及的话题,特别是对于那些试图完善其 API 策略的团队来说.一个设计良好的 API 的好处包括:改进开发者体验.更快速地编写文档以及更高效地推广你的 API.但是,到 ...
- 【Python&RS】遥感影像的像素坐标转地理坐标(仿射变换)
GDAL(Geospatial Data Abstraction Library)是一个在X/MIT许可协议下的开源栅格空间数据转换库.它利用抽象数据模型来表达所支持的各种文件格式 ...
- 自然语言处理 Paddle NLP - 词向量应用展示
词向量(Word embedding),即把词语表示成实数向量."好"的词向量能体现词语直接的相近关系.词向量已经被证明可以提高NLP任务的性能,例如语法分析和情感分析. Padd ...
- Git使用教程(带你玩转GitHub)
Git使用教程(理论实体结合体系版) 下载安装: 按照这个博客来就好 Windows系统Git安装教程(详解Git安装过程) - 学为所用 - 博客园 (cnblogs.com) Git命令大全: G ...
- load initialize总结
load initialize 方法的区别1.调用的方式 - load 根据函数地址调用 - initialize 通过objc_msgsend调用 2.调用时刻 - load runtime 加载类 ...
- 帮老娘导入SF信息
转自自己的QQ空间 2023/1/3 老娘公司要统计Excel 简单说就是把顺丰上面寄的85个快递填到表里去 再把没有寄的从那两张表加起来130多个人里面揪出来单独填表 有些企业的Excel就是个灾难 ...
- CMU15-445 Project3 Query Execution心得
Project3 Query Execution 心得 一.概述 首先要说:这个 project很有趣很硬核!从这个 project 开始才感觉自己在数据库方面真正成长了! 第一个 project : ...