1. UIKit
UIKit是iOS开发者最常用和熟悉的UI框架,承担着UI显示和用户交互的功能。UIView
在UIKit框架中最基本的单元,苹果官方文档描述UIView
的基本功能:
-
绘制和动画(Drawing and animation)
-
布局和子视图管理(Layout and subview management)
-
事件处理(Event handling)
UIView作为UIResponder的子类,能够响应和处理事件。
UIView
本身其实并不支持图层的绘制,显示和管理,这些能力是来自于CALayer
。
@interface UIView : UIResponder
@property(nonatomic,readonly,strong) CALayer *layer;
// returns view's layer. Will always return a non-nil value. view is layer's delegate
@end
CALayer
是Core Animation
的核心类,提供了展示图像和动画的能力。而CALayer
展示的内容可以是图片或者视频,也可以是运行时绘制的图像。图片的处理则依赖于Core Image
,运行时绘制图像的能力则依赖于Core Graphic
。
2. iOS 渲染框架
下图所示为 iOS App 的图形渲染技术栈,App 使用 Core Graphics
、Core Animation
、Core Image
等框架来绘制可视化内容,这些软件框架相互之间也有着依赖关系。这些框架都需要通过 OpenGL
来调用 GPU
进行绘制,最终将内容显示到屏幕之上。
-
Core Animation
Core Animation
源自于Layer Kit
,基本功能是渲染,组合和动画Core Animation
是一个复合引擎,其职责是 尽可能快地组合屏幕上不同的可视内容,这些可视内容可被分解成独立的图层(即 CALayer),这些图层会被存储在一个叫做图层树
的体系之中。从本质上而言,CALayer
是用户所能在屏幕上看见的一切的基础。 -
Core Graphic
Core Graphics
基于Quartz
高级绘图引擎,主要用于运行时绘制图像。开发者可以使用此框架来处理基于路径的绘图,转换,颜色管理,离屏渲染,图案,渐变和阴影,图像数据管理,图像创建和图像遮罩以及 PDF 文档创建,显示和分析。 -
Core Image
Core Image
与Core Graphics
恰恰相反,Core Graphics
用于在 运行时创建图像,而Core Image
是用来处理运行前创建的图像 的。Core Image
框架拥有一系列现成的图像过滤器,能对已存在的图像进行高效的处理。 -
OpenGL ES
OpenGL ES(OpenGL for Embedded Systems,简称 GLES)
,是OpenGL
的子集。OpenGL
是一套第三方标准,函数的内部实现由对应的GPU
厂商开发实现。 -
Metal
Metal
类似于OpenGL ES
,也是一套第三方标准,具体实现由苹果实现。大多数开发者都没有直接使用过 Metal,但其实所有开发者都在间接地使用 Metal。Core Animation、Core Image、SceneKit、SpriteKit 等等渲染框架都是构建于 Metal 之上的。
3. UIView 和 CALayer的关系
CALayer
是UIView
的backing store,为UIView
提供了呈现图像的能力。而UIView
继承于UIResponder
, 扩展了交互的能力。
UIView
通过视图树管理和布局视图,CALayer
通过图层树来管理和布局图层,这两者是一致的。当在视图树中添加或者删除视图,在对应图层树中也有一致的操作。
3.1 那么为什么 iOS 要基于 UIView 和 CALayer 提供两个平行的层级关系呢?
-
职责分离,这样也能避免很多重复代码
-
在 iOS 和 Mac OS X 两个平台上,事件和用户交互有很多地方的不同,基于多点触控的用户界面和基于鼠标键盘的交互有着本质的区别,这就是为什么 iOS 有
UIKit
和UIView
,对应 Mac OS X 有AppKit
和NSView
的原因。它们在功能上很相似,但是在实现上有着显著的区别。
3.2 CALayer的寄宿图
CALayer
呈现的图像内容,保存在contents
属性中,可以存放位图数据,称为寄宿图。
@interface CALayer : NSObject
/** Layer content properties and methods. **/
/* An object providing the contents of the layer, typically a CGImageRef,
* but may be something else. (For example, NSImage objects are
* supported on Mac OS X 10.6 and later.) Default value is nil.
* Animatable. */
@property(nullable, strong) id contents;
@end
contents
可以直接保存解码后的位图数据,也可以保存运行时绘制的位图数据。
1. 直接保存图片数据
// contents属性是id类型,在iOS中只能传递CGImageRef类型,所有需要强转
view.layer.contents = (__bridge id) image.CGImage;
2. 保存运行时绘制的图像
实际开发中,一般通过继承 UIView
并实现 -drawRect:
方法来自定义绘制。绘制的结果生成为CABackingStore
对象,存在contents
属性中。
UIView
不实现drawRect
不会生成寄宿图,因此避免性能问题,在不需要自定义绘制视图时,不要重写drawRect
.
3.3 UIView为CALayer扩展用户交互的能力
在可交互式系统中,应用所呈现的图像需要跟随用户的交互而变化,而UIView
正好为CALayer
扩展用户交互的能力。
CALayer
的UIView
的一个属性,UIView
是CALayer
的delegate
,实现了CALayerDelegate
协议。
@protocol CALayerDelegate <NSObject>
@optional
/* 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). */
- (void)displayLayer:(CALayer *)layer;
/* If defined, called by the default implementation of -drawInContext: */
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
/* 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. */
- (void)layerWillDraw:(CALayer *)layer
CA_AVAILABLE_STARTING (10.12, 10.0, 10.0, 3.0);
/* 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. */
- (void)layoutSublayersOfLayer:(CALayer *)layer;
/* If defined, called by the default implementation of the
* -actionForKey: method. Should return an object implementating 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.) */
- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;
@end
- 随着交互,
UIView
调用-setNeedsDisplay
,希望CALayer
更新内容
/* Marks that -display needs to be called before the layer is next
* committed. If a region is specified, only that region of the layer
* is invalidated. */
- (void)setNeedsDisplay;
- (void)setNeedsDisplayInRect:(CGRect)r;
CALayer
在数据提交之前会调用-display
方法
/* Reload the content of this layer. Calls the -drawInContext: method
* then updates the `contents' property of the layer. Typically this is
* not called directly. */
- (void)display;
CALayer
的-display
方法,首先会尝试调用delegate
就是UIView
的-displayLayer:
方法,此时代理可以直接设置contents
属性。
- (void)displayLayer:(CALayer *)layer;
- 如果代理没有实现
-displayLayer:
方法,CALayer
则会尝试调用-drawLayer:inContext:
方法。在调用该方法前,CALayer
会创建一个空的寄宿图(尺寸由 bounds 和 contentScale 决定)和一个Core Graphics
的绘制上下文,为绘制寄宿图做准备,作为 ctx 参数传入。-drawLayer:inContext:
最终会调用到-drawRect:
- 最后,由
Core Graphics
绘制生成的寄宿图会存入backing store
。
4. Core Animation 流水线
CALayer
的图像数据最终会提交到GPU去渲染,下面介绍一下 Core Animation
流水线的工作原理。
事实上,App 本身并不负责渲染,渲染则是由一个独立的进程负责,即 Render Server
进程。
App 通过 IPC 将渲染任务及相关数据提交给 Render Server
。Render Server
处理完数据后,再传递至 GPU
。最后由 GPU
调用 iOS
的图像设备进行显示。
Core Animation
流水线的详细过程如下:
-
首先,App 响应并处理事件,更新图层树,修改view的frame,color等操作
-
接着,App 通过 CPU 完成对显示内容的计算,如:视图的创建、布局计算、图片解码、文本绘制等。在完成对显示内容的计算之后,app 对图层进行打包,并在下一次 RunLoop 时将其发送至
Render Server
,即完成了一次Commit Transaction
操作。 -
Render Server
主要执行 Open GL、Core Graphics 相关程序,并调用 GPU -
GPU 则在物理层上完成了对图像的渲染。
-
GPU 通过 Frame Buffer、视频控制器等相关部件,将图像显示在屏幕上
4.1 Commit Transaction
CoreAnimation
中监听 Runloop 的BeforeWaiting
的RunloopObserver
,通过RunloopObserver
来进一步调用Core Animation
内部的CA::Transaction::commit()
。因此,Commit Transaction
操作是在 Runloop 即将进入空闲状态时执行的。
Core Animation
会遍历图层树,对需要重新布局,重新绘制的视图重新布局和绘制以及对需要解码的图片等解码。
Commit Transaction
可以细分为4个步骤,Layout
,Display
,Prepare
,Commit
:
-
Layout
这个阶段会对需要重新布局view(调用过
-setNeedsLayout
的view)重新布局,调用view的-layoutSubviews
-
Display
这个阶段主要对view(调用过
-setNeedsDisplay
的view)重新绘制,会调用到view的-drawRect:
-
Prepare
这个阶段属于附加步骤,一般处理图像的解码和转换等操作。
-
Commit
这个阶段主要将图层进行打包,并将它们发送至
Render Server
。该过程会递归执行,因为图层和视图都是以树形结构存在。
4.2 Render Server
Render Server
是不同于 App 的另一个专门处理图形渲染和调用 GPU 的进程。App 在 Commit Transaction
阶段将图层树打包通过 IPC
传递给 Render Server
. 一旦打包的图层和动画到达 Render Server
,他们会被反序列化来形成另一个叫做 渲染树 的图层树
App 和
Render Server
之间通过IPC
通信,如果图层树过于复杂,IPC
将非常耗时。
Render Server
主要有两个工作:
-
针对动画,为每一帧所有的图层属性计算中间值 (这个步骤由CPU处理)
-
调用 GPU 渲染每一帧数据,对于动画,还会渲染每一个中间帧
4.3 GPU 渲染工作
CPU将计算好的图元数据和纹理数据交由GPU处理,而GPU会这些数据渲染为可用于显示器显示的一帧帧的像素数据,渲染后的数据保存在帧缓冲区中。