iOS核心动画高级技巧 总结

读了iOS核心动画高级技巧这本书后,学到并巩固了很多知识。也让原先知道这么用的,但却不明白为什么的,懂得了它的原理。以下是对这本书中的内容的一些问答和总结

  • UIView和CALayer是什么关系,它们之间有什么区别?

    • 关系:
      • UIView仅仅是对它的一个封装,提供了一些iOS类似于处理触摸的具体功能,以及Core Animation底层方法的高级接口;
      • CALayer是UIView内部实现细节,图层才是真正用来在屏幕上显示和做动画
    • 不同点:最大的不同是CALayer不处理用户的交互,CALayer并不清楚具体的响应链(iOS通过视图层级关系用来传送触摸事件的机制),于是它并不能够响应事件。
    • 相同点:同样是一些被层级关系树管理的矩形块,同样也可以包含一些内容(像图片,文本或者背景色),管理子图层的位置
  • bounds 和 frame 有什么区别,它们什么时候一样,什么时候不一样?

    • 区别:
      • frame并不是一个非常清晰的属性,它其实是一个虚拟属性,是根据bounds,position和transform计算而来。
      • bounds 是图层的内部属性,不会因图层的位置改变为改变,只受图层的大小而改变。
    • 在原点且不旋转时是一致的。
  • zPosition 可以用来干什么?如果使用了,需要注意点什么?

    • 可以用来改变显示的层级,zPosition大的在最上层,用户可以看到。
    • 需要注意的是,虽然视觉层次改变了,事件的响应顺序还是按照图层树的顺序来响应的。
  • 判断一个图层是否被点击,你是用什么来做的?是用containsPoint么?如果是,会有什么问题,如果不是,那么你用的是什么?

    • 使用containsPoint需要一层一层的去寻找被点击的图层,每次都需要转换坐标系,多层的情况会比较麻烦。
    • 可以在touch代理中手动调用hitTest:来直接返回一个图层,再判断改图层是否是你想要的,简单而高效。
  • 什么情况下会产生离屏渲染?

    • 圆角或者不规则的角
    • 阴影
    • 蒙板
  • shouldRasterize 一般什么时候会用到?,需要注意什么?

    • 需要进行组配置的时候,比如父视图和所有的子视图设置共同的透明度时。
    • 需要对复杂图层进行扁平化存储时。
    • 需要给rasterizationScale赋值Screen.scale,来消除多倍屏上的像素点。
  • 隐式动画是什么?它和显式动画有什么区别?

    • Layer 层上的一些属性的变化会产生一个默认0.25秒,循环一次的动画为隐式动画。
    • 显示动画的基础也是隐式动画,可以理解为,我们可控的动画为显式动画。即对隐式动画的封装和修改。
  • 怎么禁止隐式动画?

    • 可以通过事务,在事务中setDisableActions:来禁止。这样做的原因是,如果不在事务中进行修改的话,可能会影响到其他地方。事务通过入栈出栈的行为防止了事务中的行为外漏。
    • 我们还可以通过实现图层的actionForLayer:forKey:委托方法,或者提供一个actions字典来控制隐式动画,例如actions方法见代码1:
代码1
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
self.colorLayer.actions = @{@"backgroundColor": transition};
//add it to our view
[self.layerView.layer addSublayer:self.colorLayer];
  • Layer 的 presentationLayer 和 modelLayer 有了解过么?分别表示了什么?

    • presentationLayer:实时的layer,对一个正在运动的图层来说,我们需要获取它的点击位置,最好使用该图层。
    • modelLayer:即将到达的图层,图层动画的终点。
  • 图层的speed改变对timeOffset和beginTime有什么影响?

    • speed 会对beginTime 造成影响,例如一个一秒的动画,如果speed(默认1.0)为2.0时,beginTime为0.5时 会直接到动画的末尾,0.5 * 2.0 = 1.0;但是timeOffset 并不会受到影响。
  • 高效绘图

    • 尽量别使用UIView的drawRect:方法和CALayerDelegate中的drawLayer:inContext:方法。如果使用了,图层会创建一个绘制的上下文,这个上下文所占用的内存为:图层宽像素*图层高像素*4字节。例如在iphone 7S 全屏幕进行绘制,所需要的内存为:414 * 3 * 736 * 3 * 4 / 1024 / 1024 = 10.5M 左右的内存,图层每次重绘的时候都需要重新抹掉内存然后重新分配。
    • 如果避免不了绘图,可以使用CAShapeLayer来绘制不规则图形、CAGradientLayer来绘制渐变、CATextLayer来绘制文本等。这避免了图层再次创建一个寄宿图,节省了内存,同时也减少了CPU的负担,因为这些图层使用GPU来硬件加速。
    • 当用特殊图层满足不了需求,使用Core Graphics来进行绘图时,可以使用setNeedsDisplayInRect:来减少对脏矩形的重绘,每次在drawRect:中只重新绘制setNeedsDisplayInRect:传入的区域。
    • GPU每一帧可以绘制的像素有一个最大限制,GPU会放弃绘制那些完全被其他图层遮挡的像素,但是要计算出一个图层是否被遮挡也是相当复杂并且会消耗处理器资源。同样,合并不同图层的透明重叠像素(即混合)消耗的资源也是相当客观的。所以为了加速处理进程,不到必须时刻不要使用透明图层。
    • 另外,适当的使用shouldRasterize可以将一个固定的图层体系折叠成单张图片,这样就不需要每一帧重新合成了,也就不会有因为子图层之间的混合和过度绘制的性能问题了。
  • 动画执行的大概过程

    • 布局 - 这是准备你的视图/图层的层级关系,以及设置图层属性(位置,背景色,边框等等)的阶段。
    • 显示 - 这是图层的寄宿图片被绘制的阶段。绘制有可能涉及你的-drawRect:和-drawLayer:inContext:方法的调用路径。
    • 准备 - 这是Core Animation准备发送动画数据到渲染服务的阶段。这同时也是Core Animation将要执行一些别的事务例如解码动画过程中将要显示的图片的时间点。
    • 提交 - 这是最后的阶段,Core Animation打包所有图层和动画属性,然后通过IPC(内部处理通信)发送到渲染服务进行显示。
    • 之后交给APP外的系统的OpenGL 进行一系列的渲染。
  • GPU

    • 大多数CALayer的属性都是用GPU来绘制
    • GPU 难以处理的部分
      • 过量的几何图形(超过几百万个三角板时)
      • 部分的离屏渲染(有些离屏渲染会发生在GPU)
      • 重绘时,经常发生在多个试图叠加并包含透明度时。
      • 过大的图片,超出2048x2048或者4096x4096时,需要用CPU在图层每次显示之前对图片预处理。
  • CPU

    • 大多数工作在Core Animation的CPU都发生在动画开始之前,所以它不会影响到帧率。
    • 会延迟动画开始的时间的操作
      • 布局,特别是自动布局
      • Core Graphics绘制实现了drawRect:方法,或者CALayerDelegate的drawLayer:inContext:时,因为需要额外的创建一个寄宿图,图越大,消耗的内存越大,CPU也就越难处理,在性能要求高时不要这么做。
      • 解压图片时,在下面的延迟解压中会提到。
  • 延迟解压

    • imageWithContentsOfFile:会延迟解压图片,直到加载到内存中,这就会在准备绘制图片的时候影响性能,因为需要在绘制之前进行解压(通常是消耗时间的问题所在)。
    • 使用imageNamed:会在加载图片之后立即进行解压,一般不会存在问题。
    • 如果不使用imageNamed:(它在内存中自动缓存了解压后的图片,即使你自己没有保留对它的任何引用),那么可以使用把整张图片绘制到CGContext的方式。见代码2。
代码2
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, YES, 0);
[image drawInRect:imageView.bounds];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();                        
  • 图层
    • 初始化图层,处理图层,打包通过IPC发给渲染引擎,转化成OpenGL几何图形,这些是一个图层的大致资源开销
    • 寄宿图可以通过Core Graphics直接绘制,也可以直接载入一个图片文件并赋值给contents属性,或事先绘制一个屏幕之外的CGContext上下文
    • 想要切圆角,不必使用cornerRadius和maskToBounds,因为这会触发离屏渲染,消耗性能。可以使用bezierPathWithRoundedRect:cornerRadius配合CAShapeLayer来实现。或者使用contensCenter。contensCenter相对于CAShapeLayer在规则如圆角时性能差不多,但是绘制不规则边框时contensCenter性能更好。

推荐阅读更多精彩内容