iOS-YZWL公司面试题

357 阅读6分钟
原文链接: www.jianshu.com
目录
  • UIWindow,UIView,CALayer的区别
  • 事件传递和响应机制
  • UIView block动画实现原理
一 UIWindow,UIView,CALayer的区别
1.1 UIWindow
@interface UIWindow : UIView

@property(nonatomic) UIWindowLevel windowLevel;                   // default = 0.0
@property(nonatomic,readonly,getter=isKeyWindow) BOOL keyWindow;
- (void)becomeKeyWindow;                               // override point for subclass. Do not call directly
- (void)resignKeyWindow;                               // override point for subclass. Do not call directly

- (void)makeKeyWindow;
- (void)makeKeyAndVisible;                             // convenience. most apps call this to show the main window and also make it key. otherwise use view hidden property

@property(nullable, nonatomic,strong) UIViewController *rootViewController NS_AVAILABLE_IOS(4_0);  // default is nil
@end

继承自UIView,是一种特殊的 UIView通常在一个app中只会有一个keyUIWindow。

iOS程序启动完毕后,创建的第一个视图控件就是UIWindow,接着创建控制器的view,最后将控制器的view添加到UIWindow上,于是控制器的view就显示在屏幕上了

主要作用是提供一个区域用来显示UIView;将事件分发给UIView;与UIViewController一起处理屏幕的旋转事件。

相关参考 iOS开发UI篇—UIWindow简单介绍

1.2 UIView
@interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, UIFocusItemContainer, CALayerDelegate>

@property(nonatomic,readonly,strong) CALayer  *layer;
@end
@interface UIResponder : NSObject <UIResponderStandardEditActions>

继承自UIResponder,间接继承自NSObject,主要是用来构建用户界面的,并且可以响应事件。

对于UIView,侧重于对内容的显示管理;其实是相对于CALayer的高层封装。

相关参考 iOS学习——UIView的研究

1.3 CALayer
@interface CALayer : NSObject <NSSecureCoding, CAMediaTiming>

直接继承自NSObject,所以不能响应事件

其实就是一个图层,UIView之所以能显示在屏幕上,主要是它内部有一个CALayer对象。在创建UIView时,它内部会自动创建一个图层,当UIView需要显示在屏幕上的时候,会调用drawRect:方法进行绘图,并且会将所有内容绘制到自己的图层上,绘图完毕后,系统会将图层拷贝到屏幕上,这样完成UIView的显示。

image
  • layer给view提供了基础设施,使得绘制内容和呈现更高效动画更容易、更低耗
  • layer不参与view的事件处理、不参与响应链

相关参考 【重读iOS】认识CALayer

二 事件传递和响应机制

推荐参考 史上最详细的iOS之事件的传递和响应机制-原理篇

备注:敢号称史上最强,并且阅读量超 8 万,评论达 180,赞赏 6,应该有其参考价值吧。
三 UIView block动画实现原理

在了解UIView block动画实现原理之前,需要先了解CALayer的可动画属性。

3.1 CALayer的可动画属性

CALayer拥有大量的属性,看CALayer的头文件内容,会发现很多的属性的注释中,最后会有一个词叫做Animatable,直译过来是可动画的。下面的截图只是CALayer众多可动画属性中的一部分(注意frame并不是可动画的属性)

/* The bounds of the layer. Defaults to CGRectZero. Animatable. */

@property CGRect bounds;

/* The position in the superlayer that the anchor point of the layer's
 * bounds rect is aligned to. Defaults to the zero point. Animatable. */

@property CGPoint position;

/* The Z component of the layer's position in its superlayer. Defaults
 * to zero. Animatable. */

@property CGFloat zPosition;

/* Defines the anchor point of the layer's bounds rect, as a point in
 * normalized layer coordinates - '(0, 0)' is the bottom left corner of
 * the bounds rect, '(1, 1)' is the top right corner. Defaults to
 * '(0.5, 0.5)', i.e. the center of the bounds rect. Animatable. */

@property CGPoint anchorPoint;

/* The Z component of the layer's anchor point (i.e. reference point for
 * position and transform). Defaults to zero. Animatable. */

@property CGFloat anchorPointZ;

/* A transform applied to the layer relative to the anchor point of its
 * bounds rect. Defaults to the identity transform. Animatable. */

@property CATransform3D transform;

如果一个属性被标记为Animatable,那么它具有以下两个特点:

1、直接对它赋值可能产生隐式动画;
2、我们的CAAnimation的keyPath可以设置为这个属性的名字。

当我们直接对可动画属性赋值的时候,由于有隐式动画存在的可能,CALayer首先会判断此时有没有隐式动画被触发。它会让它的delegate(没错CALayer拥有一个属性叫做delegate)调用actionForLayer:forKey:来获取一个返回值,这个返回值在声明的时候是一个id对象,当然在运行时它可能是任何对象。这时CALayer拿到返回值,将进行判断:

  • 如果返回的对象是一个nil,则进行默认的隐式动画;
  • 如果返回的对象是一个[NSNull null] ,则CALayer不会做任何动画;
  • 如果是一个正确的实现了CAAction协议的对象,则CALayer用这个对象来生成一个CAAnimation,并加到自己身上进行动画。

理解完这些,我们再来分析UIView的block动画就容易理解了。

3.2 UIView的block动画
Amazing things happen when they are in a block.

有趣的是,如果这个CALayer被一个UIView所持有,那么这个CALayer的delegate就是持有它的那个UIView。

大家应该可以思考出这样的问题:为什么同样的一行代码在block里面就有动画在block外面就没动画,就像下面这样:

/** 产生动画 */
- (void)createAnimation {
    UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];
    
    // 这样写没有动画
    redView.center = CGPointMake(200, 300);
    
    
    [UIView animateWithDuration:1.25 animations:^{
        // 写在block里面就有动画
        redView.center = CGPointMake(200, 300);
    }];
}
3.gif

既然UIView就是CALayer的delegate,那么actionForLayer:forKey:方法就是由UIView来实现的。所以UIView可以相当灵活的控制动画的产生。

当我们对UIView的一个属性赋值的时候,它只是简单的调用了它持有的那个CALayer的对应的属性的setter方法而已,根据上面的可动画属性的特点,CALayer会让它的delegate(也就是这个UIView)调用actionForLayer:forKey:方法。实际上结果大家都应该能想得到:在UIView的动画block外面,UIView的这个方法将返回NSNull,而在block里面,UIView将返回一个正确的CAAction对象(这里将不深究UIView是如何判断此时setter的调用是在动画block外面还是里面的)。

为了证明这个结论,我们将继续进行实验:

/** UIView 动画产生原理 */
- (void)uiviewAnimation {
    NSLog(@"%@",[self.view.layer.delegate actionForLayer:self.view.layer forKey:@"position"]);
    
    [UIView animateWithDuration:1.25 animations:^{
        NSLog(@"%@",[self.view.layer.delegate actionForLayer:self.view.layer forKey:@"position"]);
    }];
}

我们分别在block外面和block里面打印actionForLayer:forKey:方法的返回值,看看它究竟是什么玩意。

2019-06-11 19:45:23.973002+0800 YaZhaiInterviewQuestionDemo[93569:1953578] <null>
2019-06-11 19:45:23.973622+0800 YaZhaiInterviewQuestionDemo[93569:1953578] <_UIViewAdditiveAnimationAction: 0x600001ff0d40>

打印发现,我们的结论是正确的:在block外面,这个方法将返回一个NSNull(是尖括号的null,nil打印出来是圆括号的null),而在block里面返回了一个叫做UIViewAdditiveAnimationAction类的对象,这个类是一个私有类,遵循了苹果一罐的命名规范: xxAction,一定就是一个实现了CAAction协议的对象了。

这也就说明了为什么我们对一个view的center赋值,如果这行代码在动画block里面,就会有动画,在block外面则没有动画。

相关参考 iOS CoreAnimation专题——原理篇(二) UIView block动画实现原理