FaceBook POP原始碼解析三

醬了裡個醬發表於2019-03-13

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來說明。

相關文章