Wrappres' Studio.

解析 UIKit

字数统计: 1.1k阅读时长: 4 min
2019/05/11 Share

UIViewController

生命周期

  1. initWithCoder:
  2. awakeFromNib
  3. loadView
  4. viewDidLoad
  5. viewWillAppear:
  6. viewWillLayoutSubviews
  7. viewDidLayoutSubviews
  8. viewDidAppear:
  9. viewWillDisappear:
  10. viewDidDisappear:
  11. dealloc

说一下控制器收到内存警告会如何处理

当系统内存告急时,会接收到 didReceiveMemoryWarning。 这是属于 ViewController 的方法,当 ViewController 接收到 didReceiveMemoryWarning,首先会判断当前的 ViewController 是否还显示在 window 上,如果不在就会移除当前的 ViewController,销毁 ViewController 上面的子控件,并执行 ViewDidUnload 方法。

如何以通用的方法找到当前显示的 ViewController

  1. 判断 rootVC 是否有 presentVC,有的话就是这个 presentedViewController
  2. 判断是否是 UINavigationViewController,是的话从栈顶拿出 VC,然后再根据这个 VC 去找显示的 VC
  3. 在看是否是 TabbarController,是的话,找到当前选中的 VC,然后再根据这个 VC 去找显示的 VC
  4. 如果只是一个普通的 VC,就返回这个 VC 本身

UIView

生命周期

  1. layoutSubviews
  2. didAddSubview
  3. willRemoveSubview
  4. willMoveToSuperview
  5. didMoveToSuperview
  6. willMoveToWindow
  7. didMoveToWindow
  8. removeFromSuperview
  9. dealloc

UIView 和 CALayer 是什么关系

CALayer 负责图像动画的渲染
UIView 负责用户的交互

Bounds & Frame

frame 是在父控件坐标系的位置,是空间的外部尺寸
bounds 是相对自身坐标系的,是空间的内部尺寸。

界面更新

当操作 UI 时,比如改变了 frame、更新 UIView/CALayer 的层级、或者手动调用了 setNeedDisplay 时,这个 UIView/CALayer 就会被标记为待处理,放到一个全局容器中,苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出 Loop) 事件,回调去执行一个很长的函数:_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。

异步绘制

需要通过系统的 [view.delegate displayLayer:]实现异步绘制
代理负责生成 bitmap,然后设置 layer 的 contents 属性

如何高性能的为视图加圆角

  • 设置 CALayer 的 cornerRadius
    ps:iOS9 以后 UIImageView 设置 png 格式图片,不会触发离屏渲染
  • 设置 CALayer 的 mask

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 通过图片生成遮罩,
    UIImage *maskImage = [UIImage imageNamed:@"someimg"];
    CALayer *mask = [CALayer new];
    mask.frame = CGRectMake(0, 0, maskImage.size.width, maskImage.size.height);
    mask.contents = (__bridge id _Nullable)(maskImage.CGImage);
    view.layer.mask = mask;

    //通过贝塞尔曲线生成
    CAShapeLayer *mask = [CAShapeLayer new];
    mask.path = [UIBezierPath bezierPathWithOvalInRect:view.bounds].CGPath;
    view.layer.mask = mask;
  • 通过 Core Graphics 重新绘制带圆角的视图
    通过 CPU 重新绘制一份带圆角的视图来实现圆角效果,会大大增加 CPU 的负担,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    @implementation UIImage (CircleImage)

    - (UIImage *)drawCircleImage {
    CGFloat side = MIN(self.size.width, self.size.height);
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(side, side), false, [UIScreen mainScreen].scale);
    CGContextAddPath(UIGraphicsGetCurrentContext(),
    [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, side, side)].CGPath);
    CGContextClip(UIGraphicsGetCurrentContext());
    CGFloat marginX = -(self.size.width - side) / 2.f;
    CGFloat marginY = -(self.size.height - side) / 2.f;
    [self drawInRect:CGRectMake(marginX, marginY, self.size.width, self.size.height)];
    CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);
    UIImage *output = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return output;
    }
    @end

    //在需要圆角时调用如下
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    UIImage *img = [[UIImage imageNamed:@"image.png"] drawCircleImage];
    dispatch_async(dispatch_get_main_queue(), ^{
    view.image = img;
    });
    });
  • 通过混合图层
    此方法就是在要添加圆角的视图上再叠加一个部分透明的视图,只对圆角部分进行遮挡。图层混合的透明度处理方式与 mask 正好相反。此方法虽然是最优解,没有离屏渲染,没有额外的 CPU 计算,但是应用范围有限。

总结

  1. 在可以使用混合图层遮挡的场景下,优先使用第四种方法。
  2. 即使是非 iOS9 以上系统,第一种方法在综合性能上依然强于后两者,iOS9 以上由于没有了离屏渲染更是首选。
  3. 方法二和方法三由于使用了贝塞尔曲线,都可以应对复杂的圆角。只不过前者牺牲帧率,后者需要大量计算和增加部分内存,需要实际情况各自取舍。

UIButton 的父类是什么?UILabel 的父类又是什么

  1. UIButton -> UIControl -> UIView -> UIResponder
  2. UILabel -> UIView -> UIResponder
  3. 区别就在于 UIControlState……、UIControlEvent……

实现一个控件,可以浮在任意界面的上层并支持拖动

  1. 可以添加一个 window,然后设置 window 的层级
  2. 把 View 添加到 window 上
  3. 给 window 重写 pointInSide 属性,当范围在 view 上的时候,才返回 true,防止影响主 window 的事件
  4. 重写 view 的 touchDidMove 方法,获取上一次的 point 和当前 touch 的 point,然后调整 view 的坐标

setNeedsDisplay 和 layoutIfNeeded 两者是什么关系

setNeedsDisplay 是给当前的视图做了标记。
layoutIfNeeded 查找是否有标记,如果有标记及立刻刷新。
只有这二者合起来使用,才会起到立刻刷新的效果。

什么是响应链

CATALOG
  1. 1. UIViewController
    1. 1.1. 生命周期
    2. 1.2. 说一下控制器收到内存警告会如何处理
    3. 1.3. 如何以通用的方法找到当前显示的 ViewController
  2. 2. UIView
    1. 2.1. 生命周期
    2. 2.2. UIView 和 CALayer 是什么关系
    3. 2.3. Bounds & Frame
    4. 2.4. 界面更新
    5. 2.5. 异步绘制
    6. 2.6. 如何高性能的为视图加圆角
    7. 2.7. UIButton 的父类是什么?UILabel 的父类又是什么
    8. 2.8. 实现一个控件,可以浮在任意界面的上层并支持拖动
    9. 2.9. setNeedsDisplay 和 layoutIfNeeded 两者是什么关系
    10. 2.10. 什么是响应链