iOS UIView和CALayer是啥关系?

前言

在平时开发中,我们一般都是使用 UIView 或者其子类来构建视图,但是在设置圆角、阴影等效果的时候,会设置 UIView 中的 Layer 的属性。在如何选择 UIView 和 CALayer 之前,我们应该了解 UIView 跟 CALayer 是什么关系,它们之间是如何协同工作的。

UIView 负责响应事件,CALayer 负责绘制 UI

首先从继承关系来分析两者:UIView : UIResponderCALayer : NSObject

UIView 响应事件

UIView 继承 UIResponder,而 UIResponder 是响应者对象,实现了如下 API,所以继承自 UIResponder 的都具有响应事件的能力:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);

并且 UIView 提供了以下两个方法,来进行 iOS 中的事件的响应及传递(响应者链):

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;  

CALayer 绘制 UI

CALayer 没有继承自 UIResponder,所以 CALayer 不具备响应处理事件的能力。CALayer 是 QuartzCore 中的类,是一个比较底层的用来绘制内容的类。

UIView 对 CALayer 封装属性

UIView 中持有一个 layer 对象,同时这个 layer 对象 delegate,UIView 和 CALayer 协同工作。

平时我们对 UIView 设置 frame、center、bounds 等位置信息,其实都是 UIView 对 CALayer 进一层封装,使得我们可以很方便地设置控件的位置;例如圆角、阴影等属性, UIView 就没有进一步封装,所以我们还是需要去设置 Layer 的属性来实现功能。

Frame 属性主要是依赖:bounds、anchorPoint、transform、和position。

我们这主要说一下 anchorPoint 和 position 如何影响 Frame 的:anchorPoint 锚点是相对于当前 Layer 的一个点,position 是 Layer 中 anchorPoint 锚点在 superLayer 中的点,即 position 是由 anchorPoint 来确认的。

这里有几个通用的公式:

position.x = frame.origin.x + anchorPoint.x * bounds.size.width;  position.y = frame.origin.y + anchorPoint.y * bounds.size.height;  frame.origin.x = position.x - anchorPoint.x * bounds.size.width;  frame.origin.y = position.y - anchorPoint.y * bounds.size.height;    

故有:

1、position 是 layer 中的 anchorPoint 在 superLayer 中的位置坐标。

2、单独修改 position 与 anchorPoint 中任何一个属性都不影响另一个属性。

UIView 是 CALayer 的代理

UIView 持有一个 CALayer 的属性,并且是该属性的代理,用来提供一些 CALayer 行的数据,例如动画和绘制。

//绘制相关- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx; //动画相关- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;

动画相关

Layer 中很多属性都是 animatable 的,这就意味着修改这些属性会产生隐式动画。当是如果修改 UIView 主 Layer 的话,此时隐式动画会失效,因为:UIView 默认情况下禁止了 layer 动画,但是在 animation block 中又重新启用了它们。

当一个 animatable 属性变化时,Layer 会询问代理方法该如何处理这个动画,即需要在代理方法中返回合适的 CAAction 对象。

属性改变时 layer 会向 view 请求一个动作,而一般情况下 view 将返回一个 NSNull,只有当属性改变发生在动画 block 中时,view 才会返回实际的动作。

绘制相关

CALayer 在屏幕上绘制东西是因为 CALayer 内部有一个 contents (CGImage)的属性,contents 也被称为寄宿图。绘制相关的 API 如下:

- (void)displayLayer:(CALayer *)layer;- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx; - (void)drawRect:(CGRect)rect

drawRect 方法实现

平时为自定义 View 添加空间或者在上下文画图都会使用到这个函数,但是如果当我们实现了这个方法的时候,这个时候会生成一张寄宿图,这个寄宿图的尺寸是 layer 的宽 * 高 * contentsScale,其实算出来的是有多少像素。然后每个像素占用 4 个字节,总共消耗的内存大小为:宽 * 高 * contentsScale * 4 字节。

这里跟我们图片显示是一个道理:一张图片需要解压成位图才能显示到屏幕上,图片的颜色空间一般是 RGBA,每个像素点需要包含 RGBA 四个信息,所以一张图片解压成位图需要占用内存大小为:像素宽 * 像素高 * 4 个字节。(PS:将图片解压成位图是比较耗时的,这就是为什么通常会在子线程解压图片,然后再到主线程中显示,避免卡主主线程)

所以在使用 drawRect 方法来实现功能之前,需要看看是否有替代方案,避免产生寄宿图增加程序的内存,使用 CAShapeLayer 来绘制是一个不错的方案。

UILabel 绘制文字占用内存的情况

这里讲到绘制占用内存的情况,我们简单来了解下 Label 绘制文字占用的内存情况,实例代码如下:

//绘制一个全屏的 Label        UILabel *label1 = [[UILabel alloc] initWithFrame:self.view.bounds];    label1.text = @"11111";    [self.view addSubview:label1];        UILabel *label2 = [[UILabel alloc] initWithFrame:self.view.bounds];    label2.text = @"😀11111";    [self.view addSubview:label2];

绘制一个全屏的 Label,按理由需要占用内存:宽 * 高 * 4,iPhone 6SP 像素为:1242 * 2208,全屏差不多是占用 10 M 左右,但是 label1 大概会占用 3 M左右, label2 会占用 10 M左右;其实这里是因为如果使用黑白位图,苹果会优化颜色空间,这里每个像素就只会占用 1 个字节,比 4 字节节省 75% 的空间。

总结

通过了解 UIView 与 CALayer 是如何相互协同工作的,在之后的开发也可以选择相应的技术来实现功能,如果确定是不需要交互的,可以将 UIView 替换成 CALayer,来省去 UIView 封装带来的损耗,AsyncDisplayKit 库利用 ASDisplayNode 来替代 UIView 来节省资源。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1 CALayer IOS SDK详解之CALayer(一) http://doc.okbase.net/Hell...
    Kevin_Junbaozi阅读 10,569评论 3 23
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 13,793评论 1 32
  • Core Animation其实是一个令人误解的命名。你可能认为它只是用来做动画的,但实际上它是从一个叫做Laye...
    小猫仔阅读 9,204评论 1 4
  • 前言:CALayer基础知识。 一、简介 在iOS中,你能看得见摸得着的东西基本上都是UIView,比如一个按钮、...
    梦蕊dream阅读 5,008评论 2 7
  • 《七夕夜》 文/白州烟雨 窗外秋雨淅沥 如相思泪打湿了枫叶 水珠在叶尖上暂停 等候夜风的光临 帘前灯火幽明 孤独寂...
    白州烟雨阅读 1,891评论 0 0