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 来节省资源。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容

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