阅读Facebook POP框架 笔记(一)
在这一系列文章里,我主要会将自己阅读第三方代码的经历记录下来,尝试独立分析解剖一个框架。之前也阅读过一些第三方代码,但是实际上来说对自己的成长并没有太大的帮助,因为阅读的不细致,没有领会到代码的精髓。我希望能够通过记录笔记并发布到博客上这样的方式来一步步的学习优秀框架。第一个框架是POP,其次是AFN和SDWebImage。之所以先读POP,主要是因为我对POP 了解的很少,好奇心驱使我先阅读POP😄。
一、入门
其实阅读代码我觉得最困难的就是第一步,找不到头绪不知道从哪里看起。我是先到GitHub上查找了官方的使用介绍,先找到如何使用这个框架,这个框架最常用的有哪些方法和类。可以看出除了如何安装和导入框架外,官方主要介绍了4个类及其使用方法分别是:POPSpringAnimation、POPDecayAnimation、POPBasicAnimation、POPCustomAnimation,同时也介绍了POPAnimatableProperty、POPAnimationTracer 这两个类。根据名字判断前4个类是4种常见的动画,POPAnimatableProperty应该是可扩展的动画属性,POPAnimationTracer应该是动画过程跟踪类。
二、从POPSpringAnimation开始
首先我们先看一下POPSpringAnimation,毕竟POP 最重要是还是前4种动画类,我们先拿出一个类来看看。首先可以看出它是继承POPPropertyAnimation的。
@interface POPSpringAnimation : POPPropertyAnimation
跳转到POPPropertyAnimation 我们会发现它是继承自POPAnimation的,而POPAnimation则是继承自NSObject。也就是说POPAnimation是各种动画的基类。POPAnimation应该包含了动画的共同的基本属性和方法。
@interface POPPropertyAnimation : POPAnimation @interface POPAnimation : NSObject
观察头文件,里面包含了Animation的 代理、标识、回调等等,这些都是比较好理解的。比较好的是这个POPAnimation 是个抽象基类,也就是说说你不能直接用这个类new 一个对象,但是它提供了一个_init 方法。
- (id)init { [NSException raise:NSStringFromClass([self class]) format:@"Attempting to instantiate an abstract class. Use a concrete subclass instead."]; return nil; } - (id)_init { self = [super init]; if (nil != self) { [self _initState]; } return self; } - (void)_initState { _state = new POPAnimationState(self); } - (void)dealloc { if (_state) { delete _state; _state = NULL; }; }
其中_state 结构体在匿名分类中定义,它主要记录当前动画的信息,基本上动画的相关信息都能在结构体中找到。 POPAnimation主要实现的就是针对动画状态_state的存取,这样的好处是子类可以随时获取动画的状态。当代码读到这里,其实我还有两个疑问:
1、这个属性是干嘛用的?
@property (readonly, nonatomic) POPAnimationTracer *tracer;
2、代理为什么没有指定要遵循的协议?(这个问题很奇怪,因为我看绝大多数代码都是需要遵循某个协议的)
/** @abstract The animation delegate. @discussion See {@ref POPAnimationDelegate} for details. */ @property (weak, nonatomic) id delegate;
这两个问题在当前类中找不到答案,先去查找 POPAnimationTracer类,解决问题1。
看注释这个类是辅助单元测试和debug的。它的作用是将操作数据当做一个事件放入事件队列中,在事件队列中就记录了一个动画的详细数据过程。每个Animation过程拥有一个POPAnimationTracer。这主要是调试过程中使用的,没有这个类的话,想查看动画过程中的数据的话,代码耦合会很严重。想要解释问题2(虽然不一定是问题,边读边写,我也没看过代码),我只能跳转到POPAnimation的子类中查找答案。
三、POPPropertyAnimation
查看头文件,主要有一下几个属性:
@property (strong, nonatomic) POPAnimatableProperty *property;
/** @abstract The value to animate from. @discussion The value type should match the property. If unspecified, the value is initialized to the object's current value on animation start. */ @property (copy, nonatomic) id fromValue; /** @abstract The value to animate to. @discussion The value type should match the property. If unspecified, the value is initialized to the object's current value on animation start. */ @property (copy, nonatomic) id toValue;
还有3个参数并不是看命名很理解,得看代码才知道是什么意思。POPAnimatableProperty *property需要跳转到POPAnimatableProperty类,等一会再看,先了解下fromValue和toValue。这两个属性都是id 类型,也就是说存取的时候需要拆装箱。fromValue的setter和getter 如下:
- (id)fromValue { return POPBox(__state->fromVec, __state->valueType); } - (void)setFromValue:(id)aValue { POPPropertyAnimationState *s = __state; VectorRef vec = POPUnbox(aValue, s->valueType, s->valueCount, YES); if (!vec_equal(vec, s->fromVec)) { s->fromVec = vec; if (s->tracing) { [s->tracer updateFromValue:aValue]; } } }
从以上代码我们可以看出,1、它确实是写了个拆装箱的函数,函数内能根据valueType返回指定类型的数据。2、辅助调试类开启后会记录数据。3、数据和状态都保存在_state中(这个_state 感觉有点像redux 的全局状态树)。回头再看POPAnimatableProperty:
/** @abstract Block used to read values from a property into an array of floats. */ @property (readonly, nonatomic, copy) void (^readBlock)(id obj, CGFloat values[]); /** @abstract Block used to write values from an array of floats into a property. */ @property (readonly, nonatomic, copy) void (^writeBlock)(id obj, const CGFloat values[]); /** @abstract The threshold value used when determining completion of dynamics simulations. */ @property (readonly, nonatomic, assign) CGFloat threshold;
头文件中定义了readBlock、writeBlock、threshold(阈值)。POPPropertyAnimation的状态树_state 中记录着POPAnimatableProperty,也就是说其实readBlock、writeBlock、threshold也在状态树_state中。先看几个代码比较好的地方:
static POPStaticAnimatablePropertyState _staticStates[] = { /* CALayer */ {kPOPLayerBackgroundColor, ^(CALayer *obj, CGFloat values[]) { POPCGColorGetRGBAComponents(obj.backgroundColor, values); }, ^(CALayer *obj, const CGFloat values[]) { CGColorRef color = POPCGColorRGBACreate(values); [obj setBackgroundColor:color]; CGColorRelease(color); }, kPOPThresholdColor }, {kPOPLayerBounds, ^(CALayer *obj, CGFloat values[]) { values_from_rect(values, [obj bounds]); }, ^(CALayer *obj, const CGFloat values[]) { [obj setBounds:values_to_rect(values)]; }, kPOPThresholdPoint },
作者帮我们定义了很多结构体,放到一个数组中。这些结构体都是常见的动画属性。比如第一个结构体代表Layer的背景色变化。当创建一个POPAnimatableProperty对象时候,会先查找是否创建过POPAnimatableProperty,如果找到直接使用。如果没找到,那么查找是否是是系统提供的POPAnimatableProperty,如果不是系统提供的(在数组中找不到这个name),那么再创建。代码如下:
+ (id)propertyWithName:(NSString *)aName initializer:(void (^)(POPMutableAnimatableProperty *prop))aBlock { POPAnimatableProperty *prop = nil; static NSMutableDictionary *_propertyDict = nil; if (nil == _propertyDict) { _propertyDict = [[NSMutableDictionary alloc] initWithCapacity:10]; } prop = _propertyDict[aName]; if (nil != prop) { return prop; } NSUInteger staticIdx = staticIndexWithName(aName); if (NSNotFound != staticIdx) { POPStaticAnimatableProperty *staticProp = [[POPStaticAnimatableProperty alloc] init]; staticProp->_state = &_staticStates[staticIdx]; _propertyDict[aName] = staticProp; prop = staticProp; } else if (NULL != aBlock) { POPMutableAnimatableProperty *mutableProp = [[POPMutableAnimatableProperty alloc] init]; mutableProp.name = aName; mutableProp.threshold = 1.0; aBlock(mutableProp); prop = [mutableProp copy]; } return prop; }
看到这里可以看出来_state 貌似越来越重要。回想一下刚才还有个问题没解决,就是代理的问题。我看了眼POPSpringAnimation类,跟其父类POPPropertyAnimation思路基本一致,也就是说数据的操作运算并不在POPAnimation中。所有的数据操作和记录都在_state 里。结构体_POPPropertyAnimationState中定义了成员变量外还有一部分成员函数。请原谅我刚开始真的没看到成员函数,所以一直在找数据处理部分。先解释下上面提的一个无聊的问题。为什么没有使用协议,因为在设置代理时候判断是代理是否实现方法了。
void setDelegate(id d) { if (d != delegate) { delegate = d; delegateDidStart = [d respondsToSelector:@selector(pop_animationDidStart:)]; delegateDidStop = [d respondsToSelector:@selector(pop_animationDidStop:finished:)]; delegateDidProgress = [d respondsToSelector:@selector(pop_animation:didReachProgress:)]; delegateDidApply = [d respondsToSelector:@selector(pop_animationDidApply:)]; delegateDidReachToValue = [d respondsToSelector:@selector(pop_animationDidReachToValue:)]; } } virtual void handleDidStart() { if (delegateDidStart) { ActionEnabler enabler; [delegate pop_animationDidStart:self]; } POPAnimationDidStartBlock block = animationDidStartBlock; if (block != NULL) { ActionEnabler enabler; block(self); } if (tracing) { [tracer didStart]; } }
三、POPAnimator
在POPAnimation 头文件中有个NSObject+POP分类,里面定义了添加动画的方法。说实话这个不太好找,我刚开始一直在在找UIView 和 Calayer 的分类,但是没找到。
@implementation NSObject (POP) - (void)pop_addAnimation:(POPAnimation *)anim forKey:(NSString *)key { [[POPAnimator sharedAnimator] addAnimation:anim forObject:self key:key]; } - (void)pop_removeAllAnimations { [[POPAnimator sharedAnimator] removeAllAnimationsForObject:self]; } - (void)pop_removeAnimationForKey:(NSString *)key { [[POPAnimator sharedAnimator] removeAnimationForObject:self key:key]; } - (NSArray *)pop_animationKeys { return [[POPAnimator sharedAnimator] animationKeysForObject:self]; } - (id)pop_animationForKey:(NSString *)key { return [[POPAnimator sharedAnimator] animationForObject:self key:key]; } @end
在以上代码中提到了POPAnimator,所以跳进去看看。怀疑POPAnimator 操作state。跳进头文件里发现只有一个单例方法。原来作者将方法声明分离了,POPAnimatorPrivate定义了私有方法。当POPAnimator init 的时候会创建一个CADisplayLink,适时调用render方法。下面看一下添加动画的方法:
- (void)addAnimation:(POPAnimation *)anim forObject:(id)obj key:(NSString *)key { if (!anim || !obj) { return; } // support arbitrarily many nil keys if (!key) { key = [[NSUUID UUID] UUIDString]; } // lock OSSpinLockLock(&_lock); //尝试根据当前做动画的对象作为key,从POPAnimator的_dic字典中取出动画信息字典。 NSMutableDictionary *keyAnimationDict = (__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj); //如果没有动画信息字典则创建字典并更新信息 if (nil == keyAnimationDict) { keyAnimationDict = [NSMutableDictionary dictionary]; CFDictionarySetValue(_dict, (__bridge void *)obj, (__bridge void *)keyAnimationDict); } else { //存在动画信息字典,则根据动画的key,取出animation POPAnimation *existingAnim = keyAnimationDict[key]; if (existingAnim) { // unlock OSSpinLockUnlock(&_lock); //如果动画已经存在那就直接返回(不做响应),只有添加新的动画(动画不同或者动画原先不存在)才做出响应。 if (existingAnim == anim) { return; } [self removeAnimationForObject:obj key:key cleanupDict:NO]; // lock OSSpinLockLock(&_lock); } } //以下代码是做出响应的代码,以上都是判断是否存在动画 keyAnimationDict[key] = anim; /*核心代码*/ // 构造一个POPAnimatorItem对象, POPAnimatorItemRef item(new POPAnimatorItem(obj, key, anim)); // 将item 添加到两个list 的队尾(这两个lsit 干嘛用的从现在的代码来说还不知道) _list.push_back(item); _pendingList.push_back(item); // 重置动画的_state POPAnimationGetState(anim)->reset(true); //更新_displayLink的暂停状态,代码就不粘贴了,如果POPAnimator的观察者和_list都为空的时候置为暂停,但实际上我们刚添加了元素而且还是加锁了的应该不会为暂停。 updateDisplayLink(self); // unlock OSSpinLockUnlock(&_lock); // 准备开始动画主要是为runloop 添加观察者 [self _scheduleProcessPendingList]; }
上述代码中,我们需要详细查看[self _scheduleProcessPendingList]。我现在觉得越来越接近真相了。
- (void)_scheduleProcessPendingList { // see WebKit for magic numbers, eg http://trac.webkit.org/changeset/166540 static const CFIndex CATransactionCommitRunLoopOrder = 2000000; static const CFIndex POPAnimationApplyRunLoopOrder = CATransactionCommitRunLoopOrder - 1; // lock OSSpinLockLock(&_lock); if (!_pendingListObserver) { __weak POPAnimator *weakSelf = self; _pendingListObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting | kCFRunLoopExit, false, POPAnimationApplyRunLoopOrder, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { [weakSelf _processPendingList]; }); if (_pendingListObserver) { CFRunLoopAddObserver(CFRunLoopGetMain(), _pendingListObserver, kCFRunLoopCommonModes); } } // unlock OSSpinLockUnlock(&_lock); }
代码主要做了3件事:1、创建runloop 观察者。2、为主运行循环添加观察者。其中创建观察者是有4个参数:第一个参数应该还是分配内存相关,第二个应该是监听的状态,第三个是是否repeats,第4个不知道是啥,第5个是监听回调;看到这里我有1个疑问:为什么要指定监听kCFRunLoopBeforeWaiting | kCFRunLoopExit?上网查了下 runloop 状态主要有如下几种:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 即将进入Loop kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠 kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒 kCFRunLoopExit = (1UL << 7), // 即将退出Loop };
也就是说在main runloop 即将休眠和即将退出时才会调用_processPendingList方法。至于为什么要指定监听kCFRunLoopBeforeWaiting | kCFRunLoopExit,我在网上找到了答案:
_processPendingList内部主要是渲染待渲染动画,渲染完成后清除监听者。其实最核心的方式是下面的方法:
- (void)_renderTime:(CFTimeInterval)time item:(POPAnimatorItemRef)item { id obj = item->object; POPAnimation *anim = item->animation; POPAnimationState *state = POPAnimationGetState(anim); if (nil == obj) { // object exists not; stop animating NSAssert(item->unretainedObject, @"object should exist"); stopAndCleanup(self, item, true, false); } else { // start if needed state->startIfNeeded(obj, time, _slowMotionAccumulator); // only run active, not paused animations if (state->active && !state->paused) { // object exists; animate applyAnimationTime(obj, state, time); FBLogAnimDebug(@"time:%f running:%@", time, item->animation); if (state->isDone()) { // set end value applyAnimationToValue(obj, state); state->repeatCount--; if (state->repeatForever || state->repeatCount > 0) { if ([anim isKindOfClass:[POPPropertyAnimation class]]) { POPPropertyAnimation *propAnim = (POPPropertyAnimation *)anim; id oldFromValue = propAnim.fromValue; propAnim.fromValue = propAnim.toValue; if (state->autoreverses) { if (state->tracing) { [state->tracer autoreversed]; } if (state->type == kPOPAnimationDecay) { POPDecayAnimation *decayAnimation = (POPDecayAnimation *)propAnim; decayAnimation.velocity = [decayAnimation reversedVelocity]; } else { propAnim.toValue = oldFromValue; } } else { if (state->type == kPOPAnimationDecay) { POPDecayAnimation *decayAnimation = (POPDecayAnimation *)propAnim; id originalVelocity = decayAnimation.originalVelocity; decayAnimation.velocity = originalVelocity; } else { propAnim.fromValue = oldFromValue; } } } state->stop(NO, NO); state->reset(true); state->startIfNeeded(obj, time, _slowMotionAccumulator); } else { stopAndCleanup(self, item, state->removedOnCompletion, YES); } } } } }
首先取出POPAnimatorItemRef中的POPAnimation和要做动画的元素(一般是view或layer)obj。函数:
// start if needed state->startIfNeeded(obj, time, _slowMotionAccumulator);
印证了之前的猜想,是Animator 是数据state 的操作中心,state只负责处理和存取数据,Animator操作state,Animator像一个数据调度中心和管理者。函数:
applyAnimationTime(obj, state, time);
则是负责在特定的时间将obj 的属性值修改为state 中对应的值。至于详细的内容一时半会估计还搞不定,留到下次再研究。现在我还比较困惑_list 和 _pendinglist的关系。回头看一下,我们创建了Animator后顺带就创建了一个CAdisplaylink,然后直接将其置为pause。
[self _renderTime:time items:_list];
这里的参数是_list,而非_pendinglist。经过测试,如果采用监听后执行
[self _renderTime:time items:_pendingList];
其进行第一帧渲染的时间稳定在0.0012s 左右,如果等待CAdisplaylink 调用
[self _renderTime:time items:_list];
则第一帧出现的时间在0.0039~0.0150之间。
四、总结一下
每个Animation 都有一个状态树来存储数据和处理数据。Animator 将 对象obj(一般是view/layer) 和 Animation 绑定起来,Animator在合适的时候操作state 的数据处理方法。