1.AutoLayout
的原理,性能如何
- Auto Layout 其实就是对 Cassowary 算法的一种实现
- Auto Layout 的原理就是对线性方程组或者不等式的求解。
- 在使用 Auto Layout 进行布局时,可以指定一系列的约束,比如视图的高度、宽度等等。而每一个约束其实都是一个简单的线性等式或不等式,整个界面上的所有约束在一起就**明确地(没有冲突)**定义了整个系统的布局。
- 在涉及冲突发生时,Auto Layout 会尝试 break 一些优先级低的约束,
- 尽量满足最多并且优先级最高的约束。
- 有人分析得出
- 想要让 iOS 应用的视图保持 60 FPS 的刷新频率,我们必须在 1/60 = 16.67 ms 之内完成包括布局、绘制以及渲染等操作
- 使用 Auto Layout 对复杂的 UI 界面进行布局时(大于 30 个视图)就会对性能有严重的影响(同时与设备有关,文章中不会考虑设备性能的差异性)。
- 后面出现了 ASDK的布局引擎
- 一种可以在后台线程中运行的布局引擎
- 有缓存 计算 rect
- 大部分是对 ComponentKit的封装
2.UIView & CALayer
的区别
3.事件响应链
- 事件的传递
- 事件自下而上的传递
UIApplication ——> UIWindow ——> 子视图 ——> ... ——> 子视图
- 事件自下而上的传递
- 事件的响应
touchesBegan:withEvent
4.drawrect & layoutsubviews
调用时机
layoutSubviews:
(相当于layoutSubviews()函数)在以下情况下会被调用:- init初始化不会触发layoutSubviews。
- addSubview会触发layoutSubviews。
- 设置view的Frame会触发layoutSubviews (frame发生变化触发)。
- 滚动一个UIScrollView会触发layoutSubviews。
- 旋转Screen会触发父UIView上的layoutSubviews事件。
- 改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。
- 直接调用setLayoutSubviews。
drawrect:
(drawrect()函数)在以下情况下会被调用:drawrect:
是在UIViewController的loadView:
和ViewDidLoad:
方法之后调用.- 当我们调用
[UIFont的 sizeToFit]
后,会触发系统自动调用drawRect:
- 当设置UIView的contentMode或者Frame后会立即触发触发系统调用
drawRect:
- 直接调用
setNeedsDisplay
设置标记 或setNeedsDisplayInRect:
的时候会触发drawRect:
- 当我们操作drawRect方法的时候实际是在操作内存中存放视图的backingStore区域,用于后续图形的渲染操作
5.UI的刷新原理
[UIView setNeedsDisplay]
并没有发生当前视图立即绘制工作,打上需要重绘的脏标记,最后是在某个时机完成[UIView setLayoutIfNeed]
立即重新布局视图(下一个Runloop)[view layouIfNeeded]
当前RunLoop休眠前更新- 当我们调用UIView的
setNeedsDisplay
的方法时候,会调用layer
的同名方法,相当于在当前layer
打上绘制标记,在当前runloop
将要结束的时候,才会调用CALayer的display
方法进入到真正的绘制当中。 - CALayer的
display
方法中,首先会判断layer的delegate方法displayLayer:
是否实现,如果代理没有响应这个方法,则进入到系统绘制流程;如果代理响应了这个方法,则进入到异步绘制流程
系统绘制流程
- 在CALayer内部,系统会创建一个backingStore(可以理解为CGContextRef,drawRect中取到的currentRef就是这个东西),然后layer回判断是否有delegate,如果没有代理,就调用CALayer的
drawInContext:
方法;如果有代理,则调用layer代理的drawLayer:inContext:
方法,这一步发生在系统内部,然后在合适的时间给与我们回调一个熟悉的UIView的drawRect:
方法。也就是在系统内部的绘制之上,允许我们再做一些额外的绘制。最后CALayer把backting store(位图)传给GPU。
- 首先一个视图由 CPU 进行 Frame 布局,准备视图和图层的层级关系,查询是否有重写
drawRect:
或drawLayer:inContext:
方法,注意:如果有重写的话,这里的渲染是会占用CPU进行处理的。 - CPU 会将处理视图和图层的层级关系打包,通过 IPC(内部处理通信)通道提交给渲染服务,渲染服务由 OpenGL ES 和 GPU 组成。
- 渲染服务首先将图层数据交给 OpenGL ES 进行纹理生成和着色。生成前后帧缓存,再根据显示硬件的刷新频率,一般以设备的Vsync信号和CADisplayLink为标准,进行前后帧缓存的切换。
- 最后,将最终要显示在画面上的后帧缓存交给 GPU,进行采集图片和形状,运行变换,应用文理和混合。最终显示在屏幕上。
- 注: 在iOS中是双缓冲机制,有前帧缓存、后帧缓存,即GPU会预先渲染好一帧放入一个缓冲区内(前帧缓存),让视频控制器读取,当下一帧渲染好后,GPU会直接把视频控制器的指针指向第二个缓冲器(后帧缓存)。当你视频控制器已经读完一帧,准备读下一帧的时候,GPU会等待显示器的VSync信号发出后,前帧缓存和后帧缓存会瞬间切换,后帧缓存会变成新的前帧缓存,同时旧的前帧缓存会变成新的后帧缓存
异步绘制流程
layer的delegate如果实现了
displayLayer:
方法,就会进入到异步绘制的流程。在异步绘制的过程中,需要代理来生成对应的bitmap位图文件,并把此bitmap作为layer的contents属性
6.隐式动画 & 显示动画区别
隐式动画一直存在 如需关闭需设置;显式动画是不存在,如需显式 要开启(创建)。
显式动画是指用户自己通过beginAnimations:context:和commitAnimations创建的动画。隐式动画是指通过UIView的animateWithDuration:animations:方法创建的动画。
隐式动画是系统框架自动完成的。Core Animation在每个runloop周期中自动开始一次新的事务,即使你不显式的用[CATransaction begin]开始一次事务,任何在一次runloop循环中属性的改变都会被集中起来,然后做一次0.25秒的动画。在iOS4中,苹果对UIView添加了一种基于block的动画方法:+animateWithDuration:animations:。这样写对做一堆的属性动画在语法上会更加简单,但实质上它们都是在做同样的事情。CATransaction的+begin和+commit方法在+animateWithDuration:animations:内部自动调用,这样block中所有属性的改变都会被事务所包含。
7.什么是离屏渲染
- 当我们要在屏幕上显示内容, 至少需要一块与屏幕像素数据量一样大的 frame buffer 来作为数据存储区域 (GPU 渲染结果存储的地方)。但是此时出现了特殊情况导致渲染结果无法直接写入 frame buffer, 而是需要先暂存到另外的区域进行处理, 之后再写入到 frame buffer, 这种情况就称之为 离屏渲染。
何时触发
- iOS9后, 圆角+maskToBounds, 然后设置了背景颜色, 产生了离屏渲染,但是 圆角+maskToBounds 不设置背景色 , 是不会触发离屏渲染(单层情况下)
- 图层蒙版
- 阴影
- 光栅化
- 光栅化(Rasterization)是把顶点数据转换为片元的过程,具有将图转化为一个个栅格组成的图象的作用,特点是每个元素对应帧缓冲区中的一像素。(应用:较为广泛的应用于深度学习卷积神经网络的结构中)
- 关于 iOS 9 的优化后:可以理解为,因为只有 单层 内容需要添加圆角和裁切,所以可以不需要用到离屏渲染技术。但如果加上了背景色、边框或其他有图像内容的图层,就会产生为 多层 添加圆角和裁切,所以还是会触发离屏渲染。
为何要避免离屏渲染
- 离屏渲染发生在GPU层面上,因为离屏渲染使GPU触发Opengl多通道渲染管线,产生额外开销,所以要避免。 在触发离屏渲染时候,会增加GPU工作量,增加GPU工作量,可能会导致GPU和CPU工作耗时的总耗时超出Vsync信号(16.7毫秒)时间,导致UI卡顿或者掉帧。
- 离屏渲染会创建新的渲染缓冲区,导致内存上的开销,有多通道渲染管线,最终要把多通道的渲染结果进行合成,所有会有上下文的切换,就有 GPU 的额外开销,那么可能就会导致 UI 的卡顿和掉
8.imageName & imageWithContentsOfFile区别
imageNamed:
会在图片第一次渲染到屏幕上的时候进行解码,并缓存解码后的图片数据。缓存数据存储在全局缓存中,不会随着UIImage的释放而释放。imageWithContentsOfFile:
或imageWithData:
同样会在图片第一次渲染到屏幕上的时候进行解码。底层会调用到CGImageSourceCreateWithData()
方法,该方法可以指定是否要缓存解码后的数据,在64位机器上默认需要缓存(kCGImageSourceShouldCache
)。与上面的方法不同,这种方式创建的缓存会随着UIImage的释放而被释放掉。
9.多个相同的图片,会重复加载吗
不会,GPU有 像素点缓存的mask.
10.图片是什么时候解码的,如何优化
- 是加载到内存中,从UIImge->CGImage->CGImageSourceCreateWithData(data) 创建ImageSource变成bitmap位图,这些工作都是CoreAnimation在图片被加载到内存中存在在backingStore里,送给GPU流水线处理之前被解码.
11.图片渲染怎么优化
- UIGraphicsImageRenderer优化方案
- [超大图加载]分块加载方案(heiguoliangle.github.io/2018/04/18/…)
12.如果GPU的刷新率超过了iOS屏幕60Hz刷新率是什么现象,怎么解决
刷新超过60Hz一定会超级费电,手机发热导致降频.FPS降低,因为低能耗电量不足,无法支持GPU高刷新率