布景说明
UIView是iOS开发最根本的UI控件之一, 一切的显现控件简直都是承继于UIView, 经过不同类型的UIView, 咱们能够将文本, 图片等显现到屏幕上, 所以了解UIView的制作原理, 关于后续的自界说制作以及功能优化有很大的协助。
图画制作显现流程简图
以显现Hello world为例, 整个制作和显现流程大概如上图所示, 其中CPU层面, 首要担任 1 Layout: UI布局 文本核算 2 Display: 制作 3 Prepare: 图片编解码 4 Commit: 提交位图, GPU层面, 首要是烘托管线, 包含极点上色 图元安装 光栅化 片段上色 片段处理 FrameBuffer。
接下来, 咱们就详细解析整个流程。
UIView和CALayer的联系
1 UIView承继UIResponder, 能够呼应事情, 其内部持有一个CALayer成员layer, 一起签订了CALayerDelegate协议。
2 CALayer承继NSObject, 担任制作UIView显现的内容, 而实践的绘图作业都是Layer向其backing store里制作bitmap完结的。
3 操作View的绝大多数图形特点,其实都是直接操作的其具有的Layer特点, 比方frame, bounds, backgroundColor等等。
4 实践上UIView的显现内容是由CALayer的contents决议的, 对应的是backing store, 实践上一个bitmap类型的位图。
5 UIView为其供给内容, 以及担任处理触摸等事情, 参加呼应链, CALayer担任显现内容contents, 体现了单一责任规划准则。
CALayer是什么?
官方文档的界说是 办理依据图画的内容并允许您对该内容履行动画的目标。一般用于为 view 供给后备存储,但也能够在没有 View 的状况下运用以显现内容。
Layer的首要作业是办理您供给的可视内容,但Layer本身能够设置可视特点(例如布景色彩、边框和暗影)。
除了办理可视内容外,该Layer还保护有关内容几许的信息(例如方位、巨细和改换),用于在屏幕上显现该内容。
为什么不直接用一个UIView或CALayer处理一切事情?
首要有两点考虑:
1 责任不同
UIVIew 的首要责任是担任接纳并呼应事情;而 CALayer 的首要责任是担任显现 UI, 体现了单一责任规划准则。
2 需求复用
在macOS和App体系上,NSView 和 UIView 尽管行为类似,在完结上却有着显著的区别,却又都依靠于 CALayer 。在这种状况下,只能封装一个 CALayer 出来。
CALayer的显现基础
CALayer中有一个很重要的特点, 叫contents, 里边就供给显现的内容, 界说如下
/* An object providing the contents of the layer, typically a CGImageRef
* or an IOSurfaceRef, but may be something else. (For example, NSImage
* objects are supported on Mac OS X 10.6 and later.) Default value is nil.
* Animatable. */
/** Layer content properties and methods. **/
open var contents: Any?
翻译成中文, 大概意思便是: contents是图层内容特点和办法, 是供给层内容的目标, 一般是 CGImageRef 或 IOSurfaceRef,但也或许是其他东西。 (例如,Mac OS X 10.6 及更高版别支持 NSImage 目标。)默许值为 nil。
实践上, contents 特点保存了由设备烘托流水线烘托好的位图 bitmap(一般也被称为 backing store), 而当设备屏幕进行刷新时,会从CALayer中读取生成好的 bitmap, 从而呈现到屏幕上。
也正由于每次要被烘托的内容是被静态的存储起来的,所以每次烘托时,Core Animation 会触发调用 drawRect: 办法,运用存储好的 bitmap 进行新一轮的展示。
注: 假如是View 的图层,应防止直接设置此特点的内容。视图和图层之间的相互作用一般会导致视图在后续更新期间替换此特点的内容。
CALayer的图层树 Layer-tree
UIView和CALayer都有自己的树状结构,它们都能够有自己的SubView和SubLayer
iOS中有三种Layer tree
1 layer tree(model tree) (模型树)
便是各个树的节点的model信息, 比方常见的frame, affineTransform, backgroundColor等等, 这些model数据都是咱们在APP开发中能够配置设置的, 咱们任何关于view/layer的修正都能反应在model-tree中。
2 presentation tree (演示树)
这是一个中间层, 咱们App无法主动操作, 这个层内容是iOS体系在Render Server中生成的! CAAnimation 的中间态就都在这一层上更改特点来完结动画的分动作。
3 render tree (烘托树)
这是直接对应于提交到render server上进行显现的树。
三种Layer tree显现如下, 最后需求提交给render server的内容都是在model-tree中, 包含Animation的相关参数。
UIView制作原理
经过上面的介绍, 咱们知道了图画的显现都是经过Layer来办理的, 可是咱们在显现内容时, 却不是经过直接操作Layer层来完结, 尽管能够经过直接设置Layer的contents特点来完结, 可是比较麻烦, 比方咱们要在屏幕上显现Hello world这两个单词, 咱们会选择用UILabel来显现, 经过设置对应的text特点就能很快完结, 而UILabel也是承继UIView, 内部也是调用Layer的一些相关办法来完结制作, 制作流程图如下
制作进程图归纳如下
1 当调用[UIView setNeedsDisplay]时,实践上会直接调用底层layer的同名办法 [layer setNeedsDisplay]
2 然后会被Core Animation捕获到layer-tree的变化, 提交一个CATransaction , 然后触发Runloop的Observer回调,在回调中调用[CALayer display]进行当时视图的真正制作流程
3 [CALayer display]内部会先判别这个layer的delegate是否会呼应displayLayer:办法,假如不呼应就会进入体系制作流程中。假如能够呼应,实践上是供给了异步制作的入口,也便是给咱们进行异步制作留有余地, 咱们能够在这儿完结异步制作, 详细怎么完结, 后边会讲。
体系制作流程
本质是创立一个 backing storage 的流程
1 当[CALayer display]办法调用时, 判别是否有delegate去完结制作办法, 假如没有就触发体系制作。
2 体系制作时, 会先创立 backing storage(CGContextRef). 注意每个layer都会有一个context, 这个context指向一块缓存区被称为backing storeage。
3 假如layer有delegate, 则调用delegate的- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx办法(默许会将创立的CGContextRef传入),不然调用-[CALayer drawInContext:]办法,从而调用[UIView drawRect:]办法, 此时已经在CGContextRef环境中, 假如在drawRect中经过UIGraphicsGetCurrentContext() 获取到的便是CALayer创立的CGContextRef。
4 注意drawRect办法是在CPU履行的, 在它履行完之后, 经过context将数据(一般状况下这儿的终究成果会是一个bitmap, 类型是 CGImageRef)写入backing store, 经过rend server交给GPU去烘托,将backing store中的bitmap数据显现在屏幕上。
注: 每一个UIView的Layer都有一个对应的Backing Store作为其存储Content的实践内容, 而这些内容其实便是一个CGImage数据, 确切的说,是bitmap数据,以供GPU读取展示。
drawRect流程整理
体系制作流程中, 会调用到drawRect办法, 而在开发阶段, 与咱们打交道最多的也是drawRect办法, 因而这儿额定再整理下其调用流程。
1 当咱们调用[UIView setNeedsLayout], 底层会调用[CALayer setNeedsLayout], 然后会给图层增加一个dirty符号, 但还显现原来的内容。它实践上没做任何作业,所以屡次调用 -setNeedsDisplay并不会构成功能损。
2 然后会触发[CALayer display]办法。
3 CALayer创立一个CGContextRef, 创立一个 backing store, 然后将CGContextRef推入Graphics context stack(因而 CGContextRef是能够嵌套的), 当咱们调用UIKit的UIRectFill()等API, 会主动将制作成果放在stack栈顶的CGContextRef中, 咱们也能够直接调用UIGraphicsGetCurrent拿到当时的Grahics context栈顶的CGContextRef。
4 然后便是drawRect办法履行了, 制作的内容在CGContextRef的backing storage中。
5 这个back storage会保存在与layer-model-tree相关的特点中, 一起在 commit 时, 提交给 render server。
特殊场景 — UIImageView
当咱们运用UIImageView时, 这个View依然有一个CALayer, 可是它会直接运用CGImageRef(UIImage), 咱们传给UIImageView的UIImage中的图片或许是没有解码的, 在 CA Commit之前会有一个prepare进程, 因而, 这样会在CA-Transaction的第三步prepare中能看到如下调用栈:
- CA::Layer::prepare_commit
- Render::prepare_image
- Render::copy_image
- Render::create_image
- … decodeImage
注: UIImage其实是CGImage的一个轻量级封装, 因而在UIImageView中的UIImage目标直接将自己的CGImage图片数据作为CALayer的Content即可, 不再需求从头创立CGContetRef。
异步制作流程
参阅代码如下
/**
保护线程安全的制作状况
*/
@property (atomic, assign) ADLayerStatus status;
- (void)setNeedsDisplay {
// 收到新的制作恳求时,同步正在制作的线程本次撤销
self.status = ADLayerStatusCancel;
[super setNeedsDisplay];
}
- (void)display {
// 符号正在制作
self.status = ADLayerStatusDrawing;
if ([self.delegate respondsToSelector:@selector(asyncDrawLayer:inContext:canceled:)]) {
[self asyncDraw];
} else {
[super display];
}
}
- (void)asyncDraw {
__block ADQueue *q = [[ADManager shareInstance] ad_getExecuteTaskQueue];
__block id<ADLayerDelegate> delegate = (id<ADLayerDelegate>)self.delegate;
dispatch_async(q.queue, ^{
// 重绘撤销
if ([self canceled]) {
[[ADManager shareInstance] ad_finishTask:q];
return;
}
// 生成上下文context
CGSize size = self.bounds.size;
BOOL opaque = self.opaque;
CGFloat scale = [UIScreen mainScreen].scale;
CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL;
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
if (opaque && context) {
CGContextSaveGState(context); {
if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
CGContextFillPath(context);
}
if (backgroundColor) {
CGContextSetFillColorWithColor(context, backgroundColor);
CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
CGContextFillPath(context);
}
} CGContextRestoreGState(context);
CGColorRelease(backgroundColor);
} else {
CGColorRelease(backgroundColor);
}
// 运用context制作
[delegate asyncDrawLayer:self inContext:context canceled:[self canceled]];
// 重绘撤销
if ([self canceled]) {
[[ADManager shareInstance] ad_finishTask:q];
UIGraphicsEndImageContext();
return;
}
// 获取image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 结束任务
[[ADManager shareInstance] ad_finishTask:q];
// 重绘撤销
if ([self canceled]) {
return;
}
// 主线程刷新
dispatch_async(dispatch_get_main_queue(), ^{
self.contents = (__bridge id)(image.CGImage);
});
});
}
CALayerDelegate办法解析
经过上面的介绍, 咱们知道, 不管是体系制作还是异步制作, 都跟CALayerDelegate中的相关办法有关, 接下来就详细介绍下CALayerDelegate的相关办法
/** Delegate methods. **/
public protocol CALayerDelegate : NSObjectProtocol {
/* If defined, called by the default implementation of the -display
* method, in which case it should implement the entire display
* process (typically by setting the `contents' property). */
@available(iOS 2.0, *)
optional func display(_ layer: CALayer)
/* If defined, called by the default implementation of -drawInContext: */
@available(iOS 2.0, *)
optional func draw(_ layer: CALayer, in ctx: CGContext)
/* If defined, called by the default implementation of the -display method.
* Allows the delegate to configure any layer state affecting contents prior
* to -drawLayer:InContext: such as `contentsFormat' and `opaque'. It will not
* be called if the delegate implements -displayLayer. */
@available(iOS 10.0, *)
optional func layerWillDraw(_ layer: CALayer)
/* Called by the default -layoutSublayers implementation before the layout
* manager is checked. Note that if the delegate method is invoked, the
* layout manager will be ignored. */
@available(iOS 2.0, *)
optional func layoutSublayers(of layer: CALayer)
/* If defined, called by the default implementation of the
* -actionForKey: method. Should return an object implementing the
* CAAction protocol. May return 'nil' if the delegate doesn't specify
* a behavior for the current event. Returning the null object (i.e.
* '[NSNull null]') explicitly forces no further search. (I.e. the
* +defaultActionForKey: method will not be called.) */
@available(iOS 2.0, *)
optional func action(for layer: CALayer, forKey event: String) -> CAAction?
}
func display(_ layer: CALayer)
当图层符号其内容为需求更新 (setNeedsDisplay()) 时,调用此办法。例如,为图层设置 contents 特点
private lazy var delegate = LayerDelegate()
private lazy var sublayer: CALayer = {
let layer = CALayer()
layer.delegate = self.delegate
return layer
}()
// 调用 `sublayer.setNeedsDisplay()` 时,会调用 `sublayer.display(_:)`。
class LayerDelegate: NSObject, CALayerDelegate {
func display(_ layer: CALayer) {
layer.contents = UIImage(named: "rabbit.png")?.cgImage
}
}
func draw(_ layer: CALayer, in ctx: CGContext)
和 display(_:) 一样,可是能够运用图层的 CGContext 来完结显现的进程
// sublayer.setNeedsDisplay()
class LayerDelegate: NSObject, CALayerDelegate {
func draw(_ layer: CALayer, in ctx: CGContext) {
ctx.addEllipse(in: ctx.boundingBoxOfClipPath)
ctx.strokePath()
}
}
和 View中draw(_ rect: CGRect)的联系
1 首先调用Layer的draw(_:in:) 办法;
2 随后在super.draw(_:in:) 办法里边创立并配置好绘图环境;
3 经过Layer的super.draw(:in:) 调用 view 的 draw(:) 办法。
/// 注:此办法默许不履行任何操作,调用super.draw(_:) 与否并无影响。
override func draw(_ rect: CGRect) {
print(#function)
}
override func draw(_ layer: CALayer, in ctx: CGContext) {
print(#function)
}
// Prints "draw(_:in:)"
/// 注:此办法默许不履行任何操作,调用super.draw(_:) 与否并无影响。
override func draw(_ rect: CGRect) {
print(#function)
}
override func draw(_ layer: CALayer, in ctx: CGContext) {
print(#function)
super.draw(layer, in: ctx)
}
// Prints "draw(_:in:)"
// Prints "draw"
注: 只要当体系在检测到View 的 draw(:) 办法被完结时,才会主动调用Layer的display(:) 或 draw(_ :in:) 办法。不然就必须经过手动调用图层的 setNeedsDisplay() 办法来调用。
func layerWillDraw(_ layer: CALayer)
在 draw(_ layer: CALayer, in ctx: CGContext) 调用之前调用,能够运用此办法配置影响内容的任何图层状况(例如 contentsFormat 和 isOpaque )。
func layoutSublayers(of layer: CALayer)
和 UIView 的 layoutSubviews() 类似。当发现边界发生变化并且其sublayers或许需求从头排列时(例如经过 frame 改变巨细),将调用此办法。
func action(for layer: CALayer, forKey event: String) -> CAAction?
CALayer之所以能够履行动画,是由于它被界说在Core Animation结构中,是Core Animation履行操作的核心。
也便是说,CALayer 除了担任显现内容外,还能履行动画(其实是Core Animation与硬件之间的操作在履行,CALayer担任存储操作需求的数据,相当于Model)。
因而,运用CALayer的大部分特点都顺便动画作用。可是在UIView中,默许将这个作用给关掉了,能够经过它图层的托付办法从头敞开 ( 在View animation block中也会主动敞开 ),回来决议它动画特效的目标,假如回来的是 nil ,将运用默许隐含的动画特效。
示例 – 运用图层的托付办法回来一个从左到右移动目标的根本动画
final class CustomView: UIView {
override func action(for layer: CALayer, forKey event: String) -> CAAction? {
guard event == "moveRight" else {
return super.action(for: layer, forKey: event)
}
let animation = CABasicAnimation()
animation.valueFunction = CAValueFunction(name: .translateX)
animation.fromValue = 1
animation.toValue = 300
animation.duration = 2
return animation
}
}
let view = CustomView(frame: CGRect(x: 44, y: 44, width: UIScreen.width - 88, height: 300))
view.backgroundColor = .orange
self.view.addSubview(view)
let action = view.layer.action(forKey: "moveRight")
action?.run(forKey: "transform", object: view.layer, arguments: nil)
以上便是UIView的制作原理和流程, 接下来解析UIView的烘托和显现原理。
烘托原理
图形烘托首要是利用GPU并行运算才能,完结图形烘托并显现在屏幕的每一个像素上。
烘托进程最常用的便是光栅化,行将数据转化为可见像素的进程。GPU及相关驱动完结了图形处理的OpenGL和DirectX模型,其实OpenGL不是函数API而是一种规范,拟定了相关函数API及其完结的功能,详细的函数库由第三方来完结,一般是由显卡制造商来供给。
GPU架构模型
GPU内部包含了若干处理核来完结并发履行,其内部运用了二级缓存(L1、L2 cache),其与CPU的架构模型包含如下两种形式:分离式及耦合式
1 分离式结构
CPU 和 GPU 具有各自的存储体系,两者经过 PCI-e 总线进行连接。
这种结构的缺点在于 PCI-e 相关于两者具有低带宽和高延迟,数据的传输成了其中的功能瓶颈。现在运用非常广泛,如PC、智能手机等。
2 耦合式结构
CPU 和 GPU 同享内存和缓存。AMD 的 APU 选用的便是这种结构,现在首要运用在游戏主机中,如 PS4。
GPU烘托进程
首要包含:
1 极点上色器
包含了3D坐标系的转换,每个极点特点值设定
2 形状(图元)安装
构成根本的图形
3 几许上色器
结构新的极点来构成其他形状,如上图的另一个三角形
4 光栅化
将形状映射到屏幕的相应的像素生成片段,片段包含了像素结构一切的数据
5 片段上色器
丢掉超过视图以外的像素并上色
6 测验与混合
判别像素方位如是否在其他像素的后边及透明度等决议是否丢掉及混合
纹路
要想图形更加实在逼真需求更多的极点及色彩特点,这样就增加了功能开销,为提高成产和履行功率,经常会运用纹路来体现细节。
纹路是一个 2D 图片(乃至也有 1D 和 3D 的纹路),纹路一般能够直接作为图形烘托流水线的第五阶段(即片段上色器)的输入。
图形烘托技能栈
App 运用 Core Graphics、Core Animation、Core Image 等结构来制作可视化内容,这些软件结构相互之间也有着依靠联系。
这些结构都需求经过 OpenGL 来调用 GPU 进行制作,终究将内容显现到屏幕之上,结构如下图所示
结构介绍
1 UIKit
本身并不具备在屏幕成像的才能,其首要担任对用户操作事情的呼应(UIView 承继自 UIResponder),事情呼应的传递大体是经过逐层的视图树遍历完结的。
2 Core Animation
是一个复合引擎,其责任是尽或许快地组合屏幕上不同的可视内容,这些可视内容可被分解成独立的图层(即 CALayer),这些图层会被存储在一个叫做图层树的体系之中。从本质上而言,CALayer是用户所能在屏幕上看见的一切的基础。
3 Core Graphics
依据Quartz高档绘图引擎,首要用于运行时制作图画。开发者能够运用此结构来处理依据途径的绘图,转换,色彩办理,离屏烘托,图案,渐变和暗影,图画数据办理,图画创立和图画遮罩以及PDF文档创立,显现和剖析。
4 Core Image
与Core Graphics恰恰相反,Core Graphics用于在运行时创立图画,而Core Image是用来处理运行前创立的图画的。Core Image结构具有一系列现成的图画过滤器,能对已存在的图画进行高效的处理。
5 OpenGL(ES)
OpenGL for Embedded Systems,简称GLES,是OpenGL的子集。
6 Metal
类似于OpenGL ES,也是一套第三方规范,详细完结由苹果完结。大多数开发者都没有直接运用过Metal,但其实一切开发者都在间接地运用Metal。Core Animation、Core Image、SceneKit、SpriteKit等等烘托结构都是构建于Metal 之上的。当在真机上调试OpenGL程序时,控制台会打印出启用 Metal 的日志。依据这一点能够猜测,Apple已经完结了一套机制将OpenGL指令无缝桥接到Metal上,由Metal担任真正于硬件交互的作业。
Render Server
iOS中运用并不担任烘托而是由专门的烘托进程担任,即Render Server
首要处理流程如下:
1 由App处理事情(Handle Events),如:用户的点击操作,在此进程中app或许需求更新视图树,相应地,图层树也会被更新。
2 App经过CPU完结对显现内容的核算,如:视图的创立、布局核算、图片解码、文本制作等。在完结对显现内容的核算之后,App对图层进行打包,并在下一次RunLoop时将其发送至Render Server,即完结了一次Commit Transaction 操作。
详细commit transcation能够细分为如下进程:
A) Layout,首要进行视图构建,包含LayoutSubviews办法的重载,addSubview办法填充子视图等。
B) Display,首要进行视图制作,这儿仅仅是设置最要成像的图元数据。重载视图的drawRect:办法能够自界说UIView的显现,其原理是在drawRect:办法内部制作寄宿图,该进程运用CPU和内存。
C) Prepare,归于附加进程,一般处理图画的解码和转换等操作。
D) Commit,首要将图层打包,并将它们经过IPC发送至Render Server。该进程会递归履行,由于图层和视图都是以树形结构存在。
3 Render Server履行OpenGL、Core Graphics相关操作,如依据layer的各种特点(假如是动画特点,则会核算动画layer的特点的中间值)并用OpenGL准备烘托。
4 GPU经过Frame Buffer、视频控制器等相关组件对图层进行烘托到屏幕。
为了满足屏幕60FPS刷新率,RunLoop每次操作的时间间隔不应超过16.67ms,且上述进程需求并行履行。
烘托与RunLoop
1 iOS 的显现体系是由VSync信号驱动的,VSync信号由硬件时钟生成,每秒钟宣布60次(这个值取决设备硬件,比方iPhone真机上一般是59.97)。
2 iOS图形服务接纳到VSync信号后,会经过IPC告诉到App内。
3 App的Runloop在启动后会注册对应的CFRunLoopSource经过mach_port接纳传过来的时钟信号告诉,随后Source的回调会驱动整个Ap 的动画与显现。
注:实践调查App启动后未注册相关的VSync相关的Source,因而上述运用应该是Render Server烘托进程注册Source监听VSync信号来驱动图层的烘托,从而提交至GPU。
4 Core Animation在RunLoop中注册了一个Observer,监听了BeforeWaiting和Exit事情。这个Observer的优先级是2000000,低于常见的其他Observer。
5 当一个触摸事情到来时,RunLoop被唤醒,App中的代码会履行一些操作,比方创立和调整视图层级、设置 UIView的frame、修正CALayer的透明度、为视图增加一个动画。这些操作终究都会被CALayer捕获,并经过CATransaction提交到一个中间状况去。
6 当上面一切操作结束后,RunLoop行将进入休眠(或许退出)时,关注该事情的Observer都会得到告诉。这时Core Animation注册的那个Observer就会在回调中,把一切的中间状况合并提交到GPU去显现。
7 假如此处有动画,Core Animation会经过CADisplayLink等机制屡次触发相关流程。
显现原理
以屏幕显现播放视频为例, 其屏幕图形显现结构如图所示
1 CPU将图形数据经过总线BUS提交至GPU
2 GPU经过烘托处理转化为一帧帧的数据并提交至帧缓冲区
3 视频控制器会经过笔直同步信号VSync逐帧读取帧缓冲区的数据并提交至屏幕控制器终究显现在屏幕上。
双缓冲机制
为处理一个帧缓冲区功率问题(读取和写入都是一个无法有用的并发处理),选用双缓冲机制,在这种状况下,GPU会预先烘托一帧放入一个缓冲区中,用于视频控制器的读取。当下一帧烘托完毕后,GPU 会直接把视频控制器的指针指向第二个缓冲器,如下图所示:
双缓冲机制尽管提高了功率但也引入了画面撕裂问题,即当视频控制器还未读取完结时,即屏幕内容刚显现一半时,GPU将新的一帧内容提交到帧缓冲区并把两个缓冲区进行交流后,视频控制器就会把新的一帧数据的下半段显现到屏幕上,构成画面撕裂现象
笔直同步 (V-Sync)
为了处理画面撕裂的问题,GPU一般有一个机制叫做笔直同步( V-Sync),当敞开笔直同步后,GPU会等待显现器的VSync信号宣布后,才进行新的一帧烘托和缓冲区更新。这样能处理画面撕裂现象,也增加了画面流通度,但需求消费更多的核算资源,也会带来部分延迟。
页面卡顿掉帧
在VSync信号到来后,体系图形服务会经过CADisplayLink等机制告诉App,App主线程开始在CPU中核算显现内容,比方视图的创立、布局核算、图片解码、文本制作等。随后CPU会将核算好的内容提交到GPU去,由GPU进行改换、合成、烘托。随后GPU会把烘托成果提交到帧缓冲区去,等待下一次VSync信号到来时显现到屏幕上。由于笔直同步的机制,假如在一个VSync时间内,CPU或许GPU没有完结内容提交,则那一帧就会被丢掉,等待下一次时机再显现,而这时显现屏会保留之前的内容不变。这便是界面卡顿的原因。
功能优化
经过上面的解说, 咱们了解了UIView制作到显现的整个流程, 可是在实践开发中, 或许由于一些不当的操作, 导致整个流程耗时增加, 乃至呈现掉帧卡顿的问题, 这时候, 咱们能够经过以下几个方面来做功能优化, 保证页面显现翻滚的流通性。
CPU层面
cpu层面首要考虑降低资源耗费, 能够从以下几个方面入手
1 目标创立
目标创立会分配内存、调整特点、乃至还有读取文件(如创立UIViewController读取xib文件)等操作,比较耗费CPU资源。因而,尽量运用轻量的目标替代重量的目标,如
-
CALayer比UIView不需求呼应触摸事情。
-
假如目标不触及UI操作,则尽量放到后台线程履行。
-
功能敏感的视图目标,尽量运用代码创立而不是Storyboard来创立。
-
假如目标能够复用,能够运用缓存池来复用。
2 目标调整
如CALayer特点修正、视图层次调整、增加和移除视图等。
注: CALayer内部并没有特点办法,其内部是经过runtime动态接纳办法resoleInstanceMethod办法为目标暂时增加一个办法,并把对应特点值保存到内部的一个Dictionary字典里,一起还会告诉delegate、创立动画等。UIView的关于显现相关的特点(比方frame/bounds/transform)等实践上是CALayer特点映射来的。
3 目标毁掉
尽管目标毁掉毁掉资源不多,但累积起来也不容忽视。一般当容器类持有许多目标时,其毁掉时的资源耗费就非常显着,因而,可见用于后台线程去开释的目标移动后台线程去。
4 布局核算
视图布局核算是运用最为常见的耗费CPU资源的当地,其终究完结都会经过UIView.frame/bounds/center等特点的调整上,因而,防止CPU资源耗费尽量提早核算好布局,在需求时一次性调整好对应特点,而不要屡次、频频的核算和调整这些特点。
5 Autolayout
Auotlayout是苹果发起的技能,可在大部分状况下能很好地提高开发功率,可是其关于杂乱视图来说会带来严重的功能问题,因而关于功能要求高的视图尽量运用代码完结视图。
6 文本核算
假如页面包含许多文本,文本宽高核算会占用很大一部分资源,并且不可防止。能够经过富文本NSAttributedString的[NSAttributedString boundingRectWithSize:options:context]办法来核算文本宽高,用[NSAttributeString drawWithRect:options:context:]来制作文本,并放在后台线程履行防止堵塞主线程;或许运用CoreText依据c的跨渠道API来制作文本。
7 文本烘托
屏幕上能看到的一切文本内容控件,包含UIWebView,在底层都是经过CoreText排版、制作为Bitmap显现。常见的文本控件,如UILabel、UITextView等,其排版和制作都是在主线程进行,当显现许多文本时,CPU的压力会非常大。处理方案只要一个,便是自界说文本控件,并用TextKit或最底层的CoreText对文本异步制作。
8 图片解码
当运用UIImage或CGImageSource的那几个办法创立图片时,图片数据并不会当即解码。只要图片设置到UIImageView或许CALayer.contents中去,并且CALayer被提交到GPU前,CGImage中的数据才会得到解码,且需求在主线程履行。
处理办法:后台线程先把图片制作到CGBitmapContext中,然后从Bitmap直接创立图片。现在常见的网络图片库都自带这个功能。
9 图画制作
图画的制作一般是指用CGxx最初的办法将图画制作到画布中,然后从画布创立图片并显现这样的一个进程。这个最常见的便是[UIView drawRect:]办法,由于CoreCraphic办法一般都是线程安全的,所以图画的制作能够很容易放到后台线程进行。
GPU层面
相关于CPU来说,GPU首要便是:接纳提交的纹路和极点描绘(三角形),运用改换、混合并烘托,然后输出到屏幕上。一般你所能看到的内容,首要也便是纹路(图片)和形状(三角模拟的矢量图形)两类。因而能够从下面三方面入手。
1 纹路的烘托
一切的Bitmap,包含图片、文本、栅格化的内容,终究都要从内存提交到显存,绑定为GPU纹路。不论是提交到显存的进程,还是GPU调制和烘托纹路的进程,都要耗费不少GPU资源。
当在较短时间内显现许多图片时(如UITableView存在非常多的图片并且快速滑动时),CPU占用率很低,GPU占用非常高,因而会导致界面掉帧卡顿。有用防止此状况的办法便是尽量削减在短时间内许多图片的显现,尽或许将多张图片合并为一张进行显现。
2 视图的混合
存在多视图且多层次堆叠显现时,GPU会首先将其混合在一起。假如视图结构很杂乱,混合的进程也会耗费许多的GPU资源。为了减轻GPU的耗费,应尽量削减视图数量级层次,并在不透明的视图里标明opaque特点以防止无用的Alpha通道合成。
3 图形的生成
CALayer的border、圆角、暗影、遮罩(mask),CASharpLayer的矢量图形显现,一般会触发离屏烘托(offscreen rendering),而离屏烘托一般发生在GPU中。
当一个列表视图中存在许多圆角的CALayer且快速滑动时,会耗费许多的GPU资源,从而引发界面卡顿。为防止此种状况,能够测验敞开CALayer.shouldRasterize特点,这会把离屏烘托的操作转嫁到CPU上;最好是尽量防止运用圆角、暗影、遮罩等特点。
注: GPU屏幕烘托存在两种方法:
当时屏幕烘托(On-Screen Rendering)
正常的GPU烘托流程,GPU将烘托完结的帧放到帧缓冲区,然后显现到屏幕。
离屏烘托(Off-Screen Rendering)
会额定创立一个离屏烘托缓冲区(如保存后续复用的数据),后续仍会提交至帧缓冲区从而显现到屏幕。
离屏烘托需求创立新的缓冲区,烘托进程中会触及从当时屏幕切换到离屏环境屡次上下文环境切换,等到离屏烘托完结后还需求将烘托成果切换到当时屏幕环境,因而支付的代价较高。
以上便是UIView从制作到烘托再到显现的全进程, 作为一个有追求的iOS开发工程师, 保持iOS流通的功能是永久不变的追求。