iOS-UIView和CALayer的关系

1.响应事件

首先从继承关系来看,UIView继承于UIResponse,而CALayer继承于NSObject。UIKit使用UIResponse作为响应对象,来响应系统传递的事件并进行处理。所以UIView可以响应事件,而CALayer不具备响应事件的能力。CALayer是QuartzCore中的类,负责绘制内容

下面列举一些处理触摸事件的接口:

-(void)touchesBegan:(NSSet*)touches withEvent:(nullable UIEvent*)event

-(void)touchesMoved:(NSSet*)touches withEvent:(nullable UIEvent*)event

-(void)touchesEnded:(NSSet*)touches withEvent:(nullable UIEvent*)event

-(void)touchesCancelled:(NSSet*)touches withEvent:(nullable UIEvent*)event

-(void)touchesEstimatedPropertiesUpdated:(NSSet*)touchesNS_AVAILABLE_IOS(9_1)

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

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

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

2.初始化和Frame

一个 Layer 的 frame 是由它的 anchorPoint,position,bounds,和 transform 共同决定的,而一个 View 的 frame 只是简单的返回 Layer的 frame,同样 View 的 center和 bounds 也是返回 Layer 的一些属性。为了一探究竟,做如下测试。

自定义两个类MyView和MyLayer,分别继承于UIView和CALayer。

在MyView中重写以下方法:

- (instancetype)init {

    self= [superinit];

    if(self) {

        NSLog(@"============ MyView init!");

    }

    return self;

}

+ (Class)layerClass {

    return[MyLayerclass];

}

- (void)setFrame:(CGRect)frame {

    [supersetFrame:frame];

}

- (void)setCenter:(CGPoint)center {

    [supersetCenter:center];

}

- (void)setBounds:(CGRect)bounds {

    [supersetBounds:bounds];

}

在MyLayer中重写以下方法:

- (instancetype)init

{

    self= [super init];

    if(self) {

        NSLog(@"============= MyLayer init!");

    }

    return self;

}

+ (Class)layerClass {

    return [MyLayer class];

}

- (void)setFrame:(CGRect)frame {

    [super setFrame:frame];

}

- (void)setPosition:(CGPoint)position {

    [super setPosition:position];

}

- (void)setBounds:(CGRect)bounds {

    [super setBounds:bounds];

}

在两个类的初始化方法中都打下断点调用:

可以看到在创建MyView时会调用私有方法 [UIView _createLayerWithFrame:] 创建CALayer。然后在创建View时在View和Layer的Frame相关方法中都加上断点,可以看到调用顺序如下:

[MyLayer setBounds:]

[MyView setFrame:]

[MyLayer setFrame:]

[MyLayer setPosition:]

[MyLayer setBounds:]

由此看到创建时只调用了Layer的设置尺寸和位置,并没有调用View的setCenter:和setBounds:方法。

然后我发现当我修改了 view的bounds.size或者bounds.origin的时候也只会调用上边 Layer的一些方法。所以我大胆的猜一下,View 的 Center 和 Bounds 只是直接返回layer 对应的 Position 和 Bounds.

View中frame getter方法,bounds和center,UIView并没有做什么工作;它只是简单的各自调用它底层的CALayer的frame,bounds和position方法。

3.UIView主要是对显示内容的管理而CALayer负责显示内容的绘制

在UIView和CALayer分别重写父类方法:

在MyView重写drawRect:

- (void)drawRect:(CGRect)rect {

    [super drawRect:rect];

}

在MyLayer重写display:

- (void)display {

    [super display];

}

在两个方法中打断点并执行,得到如下结果:


可以看到UIView是CALayer的CALayerDelegate,由此可以推测是在代理方法内部[UIView(CALayerDelegate) drawLayer:inContext]调用UIView的drawRect方法,从而绘制出了UIView的内容。

4.隐式动画

每个view都有一个layer,但是也有一些不依附view单独存在的layer,如CAShapelayer。它们不需要附加到 view 上就可以在屏幕上显示内容。

基本上你改变一个单独的 layer 的任何属性的时候,都会触发一个从旧的值过渡到新值的简单动画(这就是所谓的隐式动画)。然而,如果你改变的是 view 中 layer 的同一个属性,它只会从这一帧直接跳变到下一帧。尽管两种情况中都有 layer,但是当 layer 附加在 view 上时,它的默认的隐式动画的 layer 行为就不起作用了。

在 Core Animation 编程指南的 “How to Animate Layer-Backed Views” 中,对为什么会这样做出了一个解释:

UIView默认情况下禁止了layer动画,但是在animation block中又重新启用了它们。

是因为任何可动画的 layer 属性改变时,layer都会寻找并运行合适的action来实行这个改变。在Core Animation的专业术语中就把这样的动画统称为动作 (action,或者CAAction)。

layer通过向它的delegate发送actionForLayer:forKey:消息来询问提供一个对应属性变化的action。delegate可以通过返回以下三者之一来进行响应:

它可以返回一个动作对象,这种情况下layer将使用这个动作。

它可以返回一个nil, 这样layer就会到其他地方继续寻找。

它可以返回一个NSNull对象,告诉layer这里不需要执行一个动作,搜索也会就此停止。

当layer在背后支持一个view的时候,view就是它的delegate。

5.单一职责

UIView负责用户的响应操作,CALayer负责绘制,就像一个类似公司的框架一样,把如同公司职员的各个功能层级组织起来,然后各司其职。

机制与策略分离

Unix内核设计的一个主要思想是——提供(Mechanism)机制而不是策略(Policy)。编程问题都可以抽离出机制和策略部分。机制一旦实现,就会很少更改,但策略会经常得到优化。例如原子可以看做是机制,而各种原子的组成就是一种策略。CALayer也可以看做是一种机制,提供图层绘制,你们可以翻开CALayer的头文件看看,基本上是没怎么变过的,而UIView可以看做是策略,变动很多。越是底层,越是机制,越是机制就越是稳定。机制与策略分离,可以使得需要修改的代码更少,特别是底层代码,这样可以提高系统的稳定性。

更多的不可变

稳定给你的是什么感觉?坚固?不可形变?稳定其实就是不可变。一个系统不可变的东西越多,越是稳定。所以机制恰是满足这个不可变的因素的。构建一个系统有一个指导思想就是尽量抽取不可变的东西和可变的东西分离。水是成不了万丈高楼的,坚固的混凝土才可以。更少的修改,意味着更少的bug的几率。

各司其职

即使能力再大也不能把说有事情都干了,万一哪一天不行了呢,那就是突然什么都不能干了。所以仅仅是基于分散风险原则也不应该出现全能类。各司其职,相互合作,把可控粒度降到最低,这样也可以是系统更稳定,更易修改。

漏的更少

接口应该面向大众的,按照八二原则,其实20%的接口就可以满足80%的需求,剩下的80%应该隐藏在背后。因为漏的少总是安全的,不是吗。剩下的80%专家接口可以隐藏与深层次。比如UIView遮蔽了大部分的CALayer接口,抽取构造出更易用的frame和动画实现,这样上手更容易。

总结:

1.view负责了与人的动作交互以及对layer的管理,layer则负责了所有能让人看到的东西。

2.每个 UIView 内部都有一个 CALayer 在背后提供内容的绘制和显示,并且 UIView 的尺寸样式都由内部的 Layer 所提供。两者都有树状层级结构,layer 内部有SubLayers,View 内部有SubViews.但是 Layer 比 View 多了个AnchorPoint。

3.在 View显示的时候,UIView 做为 Layer 的CALayerDelegate,View 的显示内容取决于内部的 CALayer 的 display。

4.CALayer 是默认修改属性支持隐式动画的,在给 UIView 的 Layer 做动画的时候,View 作为 Layer 的代理,Layer 通过actionForLayer:forKey:向 View请求相应的action(动画行为)。

5.layer 内部维护着三分layer tree,分别是presentLayer Tree(动画树),modeLayer Tree(模型树),Render Tree(渲染树),在做 iOS动画的时候,我们修改动画的属性,在动画的其实是 Layer 的 presentLayer的属性值,而最终展示在界面上的其实是提供 View的modelLayer。

6.单一职责的设计。

你可能感兴趣的:(iOS-UIView和CALayer的关系)