说在前头
对于许多使用过CAAnimation的开发者来说,“隐式动画”的概念应该并不陌生,简单地说,我们直接在代码中设置如下属性:
1 2 3 4 5
| layer.backgroundColor = newColor.cgColor layer.hidden = !layer.hidden layer.bounds = newRect
...
|
系统将会默认创建一段动画,来animate对应属性的变化。
若想禁用或自定义这段动画的参数,可以调用CATransaction
的类方法来实现自定义,当然,如果想要更完整的控制权,也可以创建一个CAAnimation
的实例。
这玩意我知道
是的,我们都对隐式动画有所了解,很多时候,隐式动画使得界面的更新不那么突兀。然而由于习惯了使用UIView对CALayer的封装,我在上周的开发中遇到了如下的问题:
项目中需要实现类似“飘赞”功能,当用户点击屏幕时,创建一个新的视图,播放一段帧动画,然后隐藏,由于用户可以快速点击屏幕,为了提升性能,我想到利用CALayer来进行动画的播放,并且使用一定的复用机制,这样可以节省分配/销毁内存的开销,也避免内存过度增长,代码大概长这样:
动画Layer:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| @class AnimationLayer; @protocol AnimationLayerDelegate <NSObject>
-(void)animationDidFinish:(AnimationLayer *)animLayer;
@end
@interface AnimationLayer : CALayer <CAAnimationDelegate>
-(void)animate; -(void)clean;
@property (nonatomic ,weak)id <AnimationLayerDelegate> animDelegate;
@end
@implementation AnimationLayer
-(void)animate{ self.opacity = 1.0; CABasicAnimation *anim = ...;
anim.delegate = self;
[self addAnimation:anim forKey:nil]; }
-(void)clean{ self.opacity = 0.0; self.transform = CATransform3DIdentity; ... }
-(void)dealloc{ NSLog(@"dealloc"); }
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{ [_animDelegate animationDidFinish:self]; }
|
控制器复用layer:
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 26 27 28 29 30 31
| -(void)addAnimation{ if (self.layers.count > 0) { AnimationLayer *l = self.layers[0]; [self.layers removeObjectAtIndex:0]; [l animate]; NSLog(@"Reuse Layer"); return ; }
AnimationLayer *l = [AnimationLayer new]; l.bounds = CGRectMake(0, 0, 50, 50); l.backgroundColor = [UIColor blueColor].CGColor; [self.view.layer addSublayer:l]; l.animDelegate = self;
NSLog(@"Create New Layer"); [l animate]; }
-(void)animationDidFinish:(AnimationLayer *)animLayer{ [animLayer clean]; if (self.layers.count > MAX_REUSE_LIMIT) { [animLayer removeFromSuperlayer]; } else{ NSLog(@"Add Layer To Reuse Queue");
[self.layers addObject:animLayer]; } }
|
上面的代码为Layer设置了一个AnimationDelegate,以便在动画结束后,视情况加入复用队列中,这样成功的减少了alloc/dealloc的次数,也免去了部分对于layer层级树的操作,接下来让程序跑起来吧!
在经过几次简单的测试之后,我发现了如下的控制台输出:
1 2 3 4 5 6 7 8 9 10 11
| LayerTest[1258:66921] Create New Layer LayerTest[1258:66921] Add Layer To Reuse Queue LayerTest[1258:66921] dealloc LayerTest[1258:66921] Reuse Layer LayerTest[1258:66921] dealloc LayerTest[1258:66921] Add Layer To Reuse Queue LayerTest[1258:66921] dealloc LayerTest[1258:66921] Reuse Layer LayerTest[1258:66921] dealloc LayerTest[1258:66921] Add Layer To Reuse Queue LayerTest[1258:66921] dealloc
|
Layer的创建,加入复用队列以及出队复用逻辑都是正确的,但是这么多的dealloc是什么情况?
隐式动画的实质
在一番探索之后,我才终于弄清楚这个被dealloc的对象到底是怎么被创建出来的:
从调用堆栈中,很容易可以看出,在我们的[AnimationLayer clean]
方法中,setOpacity:
触发了Implict Animation
(不得不说苹果这命名还是很直观的),而在调用CALayer
的presnetation_layer
时候,系统调用了initWithLayer:
方法,来创建一个与目标layer相同的layer来实现动画效果。
结合这篇文章,我们就能更好理解在设置属性时,UIView
与CALayer
为什么会有如此不同的表现了。
总结
UIView
是对CALayer
的封装,为我们提供了许多更便利的接口,但对于动画更多细致的控制,或许还是要通过Core Animation
来实现,而这其中由于“便利”的UIView
导致的可能出现的疏忽,还有待我慢慢发现。