FaceBook POP源码解析三

1,582 阅读7分钟

FaceBook POP源码解析一

FaceBook POP源码解析二

FaceBook POP源码解析四

一、前言

上一节讲解了POPAnimation的相关内容,并提及到动画的操作实际上是在POPAnimator中进行。本文将主要解析POPAnimator.

二、POPAnimator

POPAnimator是pop动画的核心类,负责存储和管理添加进来的动画。

1. POPAnimator的相关属性
@interface POPAnimator ()
{
  CADisplayLink *_displayLink; //定时器,用于渲染动画
  POPAnimatorItemList _list; //存储需要执行的动画
  CFMutableDictionaryRef _dict; //用于存储obj中对应的动画
  NSMutableArray *_observers; //用于存储监听者
  POPAnimatorItemList _pendingList; //用于临时存储添加的动画
  CFRunLoopObserverRef _pendingListObserver; 
  CFTimeInterval _slowMotionStartTime; //以下三者是为了校正模拟器时间的属性
  CFTimeInterval _slowMotionLastTime;
  CFTimeInterval _slowMotionAccumulator;
  CFTimeInterval _beginTime; //动画开始时间
  pthread_mutex_t _lock; //用于保证线程安全的锁
  BOOL _disableDisplayLink; 
}
@end
2.POPAnimator的初始化操作

初始化方法主要创建了定时器、存储结构_dict和锁_lock。

- (instancetype)init
{
  self = [super init];
  if (nil == self) return nil;
  _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render)];
  _displayLink.paused = YES;
  [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
  _dict = POPDictionaryCreateMutableWeakPointerToStrongObject(5);
  pthread_mutex_init(&_lock, NULL);
  return self;
}
3. POPAnimator的render操作
- (void)render
{
  CFTimeInterval time = [self _currentRenderTime];
  [self renderTime:time];
}

- (void)renderTime:(CFTimeInterval)time
{
  [self _renderTime:time items:_list];
}

我们知道当动画开始后,定时器会每隔16ms调用render方法来进行渲染动画。

- (void)_renderTime:(CFTimeInterval)time items:(std::list<POPAnimatorItemRef>)items
{
  // begin transaction with actions disabled
  [CATransaction begin];
  [CATransaction setDisableActions:YES];

  // notify delegate
  __strong __typeof__(_delegate) delegate = _delegate;
  [delegate animatorWillAnimate:self];

  // lock
  pthread_mutex_lock(&_lock);

  // count active animations
  const NSUInteger count = items.size();
  if (0 == count) {
    // unlock
    pthread_mutex_unlock(&_lock);
  } else {
    // copy list into vector
    std::vector<POPAnimatorItemRef> vector{ items.begin(), items.end() };

    // unlock
    pthread_mutex_unlock(&_lock);

    for (auto item : vector) {
      [self _renderTime:time item:item];
    }
  }

  // notify observers
  for (id observer in self.observers) {
    [observer animatorDidAnimate:(id)self];
  }

  // lock
  pthread_mutex_lock(&_lock);

  // update display link
  updateDisplayLink(self);

  // unlock
  pthread_mutex_unlock(&_lock);

  // notify delegate and commit
  [delegate animatorDidAnimate:self];
  [CATransaction commit];
}

a.[CATransaction setDisableActions:YES]的主要作用是关闭隐式动画,避免影响动画的执行。

b. updateDisplayLink方法是为了避免定时器的不必要工作,当监听者队列或动画队列中为零时,会暂停定时器的工作,直到有动画或监听者的加入为止。

static void updateDisplayLink(POPAnimator *self)
{
  BOOL paused = (0 == self->_observers.count && self->_list.empty()) || self->_disableDisplayLink;
  if (paused != self->_displayLink.paused) {
    FBLogAnimInfo(paused ? @"pausing display link" : @"unpausing display link");
    self->_displayLink.paused = paused;
  }
}

c. 遍历动画队列,逐个渲染动画

- (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 need
    //更新POPAnimationState中的active和paused属性
    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);
        }
      }
    }
  }
}

在该方法中调用了很多POPAnimationState中的方法,我们先暂时放着,先探讨下里面两个比较重要的方法:applyAnimationTimeapplyAnimationToValue

d. applyAnimationTimeapplyAnimationToValue方法

static void applyAnimationTime(id obj, POPAnimationState *state, CFTimeInterval time)
{
  if (!state->advanceTime(time, obj)) { 
    return;
  }
  
  POPPropertyAnimationState *ps = dynamic_cast<POPPropertyAnimationState*>(state);
  if (NULL != ps) {
    updateAnimatable(obj, ps);
  }
  
  state->delegateApply();
}

static void applyAnimationToValue(id obj, POPAnimationState *state)
{
  POPPropertyAnimationState *ps = dynamic_cast<POPPropertyAnimationState*>(state);

  if (NULL != ps) {
    
    // finalize progress
    ps->finalizeProgress();
    
    // write to value, updating only if needed
    updateAnimatable(obj, ps, true);
  }
  
  state->delegateApply();
}

对比两者,我们可以看到它们都调用了updateAnimatable方法:

static void updateAnimatable(id obj, POPPropertyAnimationState *anim, bool shouldAvoidExtraneousWrite = false)
{
  // handle user-initiated stop or pause; halt animation
  if (!anim->active || anim->paused)
    return;

  if (anim->hasValue()) { //判断是否有数据
    POPAnimatablePropertyWriteBlock write = anim->property.writeBlock;
    if (NULL == write)
      return;

    // current animation value
    VectorRef currentVec = anim->currentValue();
    //判断是否需要每一帧都要更新
    if (!anim->additive) { 

      // if avoiding extraneous writes and we have a read block defined
      // 若读取的数据和当前的数据一致,则没必要再写一次数据
      if (shouldAvoidExtraneousWrite) {

        POPAnimatablePropertyReadBlock read = anim->property.readBlock;
        if (read) {
          // compare current animation value with object value
          Vector4r currentValue = currentVec->vector4r();
          Vector4r objectValue = read_values(read, obj, anim->valueCount);
          if (objectValue == currentValue) {
            return;
          }
        }
      }
      
      // update previous values; support animation convergence
      anim->previous2Vec = anim->previousVec;
      anim->previousVec = currentVec;

      // write value
      // 写入数据
      write(obj, currentVec->data());
      if (anim->tracing) {
        [anim->tracer writePropertyValue:POPBox(currentVec, anim->valueType, true)];
      }
    } else {
      POPAnimatablePropertyReadBlock read = anim->property.readBlock;
      NSCAssert(read, @"additive requires an animatable property readBlock");
      if (NULL == read) {
        return;
      }

      // object value
      Vector4r objectValue = read_values(read, obj, anim->valueCount);

      // current value
      Vector4r currentValue = currentVec->vector4r();
      
      // determine animation change
      if (anim->previousVec) {
        Vector4r previousValue = anim->previousVec->vector4r();
        currentValue -= previousValue;
      }

      // avoid writing no change
      if (shouldAvoidExtraneousWrite && currentValue == Vector4r::Zero()) {
        return;
      }
      
      // add to object value
      currentValue += objectValue;
      
      // update previous values; support animation convergence
      anim->previous2Vec = anim->previousVec;
      anim->previousVec = currentVec;
      //写入数据
      // write value
      write(obj, currentValue.data());
      if (anim->tracing) {
        [anim->tracer writePropertyValue:POPBox(currentVec, anim->valueType, true)];
      }
    }
  }
}

上面的代码看似比较繁琐,但其关键之处在于POPAnimatablePropertyReadBlockPOPAnimatablePropertyWriteBlock两者。

typedef void (^POPAnimatablePropertyReadBlock)(id obj, CGFloat values[]);
typedef void (^POPAnimatablePropertyWriteBlock)(id obj, const CGFloat values[]);

这两个block的声明得很简单,分别传入动画对应的obj和更新的数值,但不同之处在于前者是从obj中读取到对应的数值,后者是将数值写入到obj属性中的。而属性对应的两个block在POPAnimationProperty.mm文件中已经声明了。

typedef struct
{
  NSString *name; //属性名
  POPAnimatablePropertyReadBlock readBlock;
  POPAnimatablePropertyWriteBlock writeBlock;
  CGFloat threshold; //阈值
} _POPStaticAnimatablePropertyState;
typedef _POPStaticAnimatablePropertyState POPStaticAnimatablePropertyState;

static POPStaticAnimatablePropertyState _staticStates[] =
{
    {kPOPLayerPositionX,
    ^(CALayer *obj, CGFloat values[]) {
      values[0] = [(CALayer *)obj position].x;
    },
    ^(CALayer *obj, const CGFloat values[]) {
      CGPoint p = [(CALayer *)obj position];
      p.x = values[0];
      [obj setPosition:p];
    },
    kPOPThresholdPoint
  },

  {kPOPLayerPositionY,
    ^(CALayer *obj, CGFloat values[]) {
      values[0] = [(CALayer *)obj position].y;
    },
    ^(CALayer *obj, const CGFloat values[]) {
      CGPoint p = [(CALayer *)obj position];
      p.y = values[0];
      [obj setPosition:p];
    },
    kPOPThresholdPoint
  },
}

我们可以看到_staticStates声明了相关属性的readBlock和writeBlock,而获取block的方式,就是通过定义的name属性。

4.addAnimation方法

我们之前提到过外部调用addAnimation方法时,实际上会调用到POPAnimator中的addAnimation方法,那么接下来我们就看动画的整个添加逻辑。

- (void)addAnimation:(POPAnimation *)anim forObject:(id)obj key:(NSString *)key
{
  if (!anim || !obj) {
    return;
  }
  // 针对key为空的情况
  if (!key) {
    key = [[NSUUID UUID] UUIDString];
  }
  // lock
  pthread_mutex_lock(&_lock);
  // obj为key,获取obj中包括的动画
  NSMutableDictionary *keyAnimationDict = (__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj);
  // 若获取到的dict为空,则创建一个dict用于存储obj的动画
  if (nil == keyAnimationDict) {
    keyAnimationDict = [NSMutableDictionary dictionary];
    CFDictionarySetValue(_dict, (__bridge void *)obj, (__bridge void *)keyAnimationDict);
  } else {
    POPAnimation *existingAnim = keyAnimationDict[key];
    if (existingAnim) {
      pthread_mutex_unlock(&_lock);
      if (existingAnim == anim) {
        return;
      }
      //需要移除obj中相同key的动画
      [self removeAnimationForObject:obj key:key cleanupDict:NO];
      pthread_mutex_lock(&_lock);
    }
  }
  keyAnimationDict[key] = anim;
  POPAnimatorItemRef item(new POPAnimatorItem(obj, key, anim));
  //添加到队列中
  _list.push_back(item);
  _pendingList.push_back(item);
  POPAnimationGetState(anim)->reset(true);
  //更新定时器的状态
  updateDisplayLink(self);
  pthread_mutex_unlock(&_lock);
  [self _scheduleProcessPendingList];
}

该方法主要是将动画分别存储到_dict、_list和_pendingList中,这里比较重要的地方是调用了_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;
  pthread_mutex_lock(&_lock);
  if (!_pendingListObserver) {
    __weak POPAnimator *weakSelf = self;
    /监听RunLoop即将进入睡眠或退出状态时的事件
    _pendingListObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting | kCFRunLoopExit, false, POPAnimationApplyRunLoopOrder, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
      [weakSelf _processPendingList];
    });

    if (_pendingListObserver) {
      CFRunLoopAddObserver(CFRunLoopGetMain(), _pendingListObserver,  kCFRunLoopCommonModes); //将监听者添加到RunLoop中
    }
  }
  pthread_mutex_unlock(&_lock);
}

这里最重要的是监听RunLoop即将进入睡眠或退出状态时,调用了_processPendingList方法。 为什么要在RunLoop即将进入睡眠或退出状态时,再去处理_pendingList队列呢,而不是立马去处理?

如果我们立马去处理_pendingList队列,对动画进行渲染的话,那么可能会出现卡顿的问题,因为我们是在主线程中对动画进行渲染的。比如当我们在滑动列表时,同时动画也在不断渲染,那么就可能出现掉帧的情况了。而如果我们在等RunLoop处理完了当前的UI事件后,再去处理动画,那么就不存在影响到其他UI的渲染了。

- (void)_processPendingList
{
  // rendering pending animations
  CFTimeInterval time = [self _currentRenderTime];
  [self _renderTime:(0 != _beginTime) ? _beginTime : time items:_pendingList];
  pthread_mutex_lock(&_lock);
  _pendingList.clear();
  [self _clearPendingListObserver];
  pthread_mutex_unlock(&_lock);
}

该方法其实就是逐一对_pendingList中的动画通过_render函数逐一渲染,接下来的流程就是我们上述所说的。

5. _list和_pendingList的区别

两者都是用来存储动画,但_list是贯穿动画的整个生命周期,只有当动画被执行完之后才会被移除。而_pendingList只是用于暂存动画,它只是在RunLoop还处于活动期间,存储被添加的动画,当RunLoop即将进入睡眠或退出状态时,它会处理_pendingList中的动画,动画都被处理(开始执行),_pendingList就会清空队列中的动画。

三、总结

本小节主要介绍了POPAnimator是如何管理被添加进来的Animation,以及如何借助POPAnimationState来更新Animation的属性值。但具体动画是如何被更新,我们会从下一章节从POPAnimationState来说明。