閱讀Facebook POP框架 筆記(一)

  在這一系列文章裏,我主要會將本身閱讀第三方代碼的經歷記錄下來,嘗試獨立分析解剖一個框架。以前也閱讀過一些第三方代碼,可是實際上來講對本身的成長並無太大的幫助,由於閱讀的不細緻,沒有領會到代碼的精髓。我但願可以經過記錄筆記併發布到博客上這樣的方式來一步步的學習優秀框架。第一個框架是POP,其次是AFN和SDWebImage。之因此先讀POP,主要是由於我對POP 瞭解的不多,好奇心驅使我先閱讀POP😄。web

  1、入門redux

  其實閱讀代碼我以爲最困難的就是第一步,找不到頭緒不知道從哪裏看起。我是先到GitHub上查找了官方的使用介紹,先找到如何使用這個框架,這個框架最經常使用的有哪些方法和類。能夠看出除了如何安裝和導入框架外,官方主要介紹了4個類及其使用方法分別是:POPSpringAnimation、POPDecayAnimation、POPBasicAnimation、POPCustomAnimation,同時也介紹了POPAnimatableProperty、POPAnimationTracer 這兩個類。根據名字判斷前4個類是4種常見的動畫,POPAnimatableProperty應該是可擴展的動畫屬性,POPAnimationTracer應該是動畫過程跟蹤類。數組

  2、從POPSpringAnimation開始併發

  首先咱們先看一下POPSpringAnimation,畢竟POP 最重要是仍是前4種動畫類,咱們先拿出一個類來看看。首先能夠看出它是繼承POPPropertyAnimation的。app

@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的存取,這樣的好處是子類能夠隨時獲取動畫的狀態。當代碼讀到這裏,其實我還有兩個疑問:oop

一、這個屬性是幹嗎用的?單元測試

@property (readonly, nonatomic) POPAnimationTracer *tracer;

 二、代理爲何沒有指定要遵循的協議?(這個問題很奇怪,由於我看絕大多數代碼都是須要遵循某個協議的)學習

/**
 @abstract The animation delegate.
 @discussion See {@ref POPAnimationDelegate} for details.
 */
@property (weak, nonatomic) id delegate;

這兩個問題在當前類中找不到答案,先去查找 POPAnimationTracer類,解決問題1。

 看註釋這個類是輔助單元測試和debug的。它的做用是將操做數據當作一個事件放入事件隊列中,在事件隊列中就記錄了一個動畫的詳細數據過程。每一個Animation過程擁有一個POPAnimationTracer。這主要是調試過程當中使用的,沒有這個類的話,想查看動畫過程當中的數據的話,代碼耦合會很嚴重。想要解釋問題2(雖然不必定是問題,邊讀邊寫,我也沒看過代碼),我只能跳轉到POPAnimation的子類中查找答案。

  3、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];
    }
  }
}

 從以上代碼咱們能夠看出,一、它確實是寫了個拆裝箱的函數,函數內能根據valueType返回指定類型的數據。二、輔助調試類開啓後會記錄數據。三、數據和狀態都保存在_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];
    }
  }
  

   3、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件事:一、建立runloop 觀察者。二、爲主運行循環添加觀察者。其中建立觀察者是有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之間。

  4、總結一下

每一個Animation 都有一個狀態樹來存儲數據和處理數據。Animator 將 對象obj(通常是view/layer) 和 Animation 綁定起來,Animator在合適的時候操做state 的數據處理方法。

相關文章
相關標籤/搜索