FaceBook Pop 源码分析 (1)

今天开始看看FaceBook Pop 源码。

基本使用

    CALayer *layer = self.label.layer;
    [layer pop_removeAllAnimations];
    
    POPBasicAnimation *anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerPositionY];
    anim.fromValue = @(100);
    anim.toValue = @(300);
    anim.completionBlock = ^(POPAnimation *anim, BOOL finished) {
        NSLog(@"Animation has completed.");
        self.tapGesture.enabled = YES;
    };
    [layer pop_addAnimation:anim forKey:@"size"];

这就是实现label 沿着y轴的移动
这里涉及类POPSpringAnimation,我们就从这个类开始。

POPBasicAnimation

先看类的属性和方法


POPBasicAnimation.png

从上图我们能看出来

1 POPBasicAnimation 继承POPPropertyAnimationPOPPropertyAnimation继承POPAnimation
2 POPBasicAnimationPOPPropertyAnimationPOPAnimation都有结构体_state 。类型分别是struct _ POPBasicAnimation Statestruct _POPPropertyAnimationStatestruct _POPAnimationState
3 struct _POPPropertyAnimationStatestruct _POPPropertyAnimationStatestruct _POPAnimationState的继承关系和类继承关系一致
4 ** POPBasicAnimation, POPPropertyAnimationPOPAnimation都有结构体变量 _state

POPBasicAnimation.png

都含- (void)_initState函数,这个函数就是用来初始化结构体_state的。

看初始化

+ (instancetype)animation
{
  return [[self alloc] init];
}

+ (instancetype)animationWithPropertyNamed:(NSString *)aName
{
  POPBasicAnimation *anim = [self animation];
  anim.property = [POPAnimatableProperty propertyWithName:aName];
  return anim;
}

这里就是让类的_state 变量进行初始化。初始化的具体过程后面再说。

这里多了个类 POPAnimatableProperty 接下来我们看看这个类的结构

POPAnimatableProperty

POPAnimatableProperty.png

这个类相关的继承关系相对简单,函数也简单,我们看看初始化

+ (id)propertyWithName:(NSString *)aName
{
  return [self propertyWithName:aName initializer:NULL];
}

+ (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;
}

1 判断全局字典是否为nil,是nil就生成字典.从字典中查找是否有name对应的动画,有就返回,没有执行2.
2 根据name 查找name所对应的静态属性。找到执行3,没有执行4
3 生成POPStaticAnimatableProperty对象,记录住name动画相关属性,并且将静态动画保存在全局字典中
4 要是没有静态动画,那么我们就生成一个POPMutableAnimatableProperty 对象,并且返回
5 返回 对象

静态动画的结构如下


AnimationProperty

这里第二步查找

static NSUInteger staticIndexWithName(NSString *aName)
{
  NSUInteger idx = 0;
  while (idx < POP_ARRAY_COUNT(_staticStates)) {
    if ([_staticStates[idx].name isEqualToString:aName])
      return idx;
    idx++;
  }

  return NSNotFound;
}

这里有个 _staticStates 数组 。定义如下

typedef struct
{
  NSString *name;
  pop_animatable_read_block readBlock;
  pop_animatable_write_block writeBlock;
  CGFloat threshold;
} _POPStaticAnimatablePropertyState;
typedef _POPStaticAnimatablePropertyState POPStaticAnimatablePropertyState;

static POPStaticAnimatablePropertyState _staticStates[]

我们发现 _staticStates 是个数组,里面装有的POPStaticAnimatablePropertyState类型的结构体。
POPStaticAnimatablePropertyState 正好对应的是POPAnimatableProperty 的四个属性。

这里我们拿出一个POPStaticAnimatablePropertyState 属性看看

 {kPOPLayerBackgroundColor,
    ^(CALayer *obj, CGFloat values[]) {
      POPCGColorGetRGBAComponents(obj.backgroundColor, values);
    },
    ^(CALayer *obj, const CGFloat values[]) {
      CGColorRef color = POPCGColorRGBACreate(values);
      [obj setBackgroundColor:color];
      CGColorRelease(color);
    },
    kPOPThresholdColor
  }

这里我们看看readBlock 和writeBlock

void POPCGColorGetRGBAComponents(CGColorRef color, CGFloat components[])
{
  if (!color) {
#if TARGET_OS_IPHONE
    color = [UIColor clearColor].CGColor;
#else
    color = [NSColor clearColor].CGColor;
#endif
  }
  
  const CGFloat *colors = CGColorGetComponents(color);
  size_t count = CGColorGetNumberOfComponents(color);
  
  if (4 == count) {
    // RGB colorspace
    components[0] = colors[0];
    components[1] = colors[1];
    components[2] = colors[2];
    components[3] = colors[3];
  } else if (2 == count) {
    // Grey colorspace
    components[0] = components[1] = components[2] = colors[0];
    components[3] = colors[1];
  } else {
    // Use CI to convert
    CIColor *ciColor = [CIColor colorWithCGColor:color];
    components[0] = ciColor.red;
    components[1] = ciColor.green;
    components[2] = ciColor.blue;
    components[3] = ciColor.alpha;
  }
}

CGColorRef POPCGColorRGBACreate(const CGFloat components[])
{
#if TARGET_OS_IPHONE
  CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
  CGColorRef color = CGColorCreate(space, components);
  CGColorSpaceRelease(space);
  return color;
#else
  return CGColorCreateGenericRGB(components[0], components[1], components[2], components[3]);
#endif
}

其实就是对layer 颜色读写。很简单不做介绍了

NSObject (POP)

我们看看动画如何加上去的

[layer pop_addAnimation:anim forKey:@"size"];

这个是给NSObject 增加了个category
看看都增加了什么方法


NSObject (POP).png

看源码实现也很简单

- (void)pop_addAnimation:(POPAnimation *)anim forKey:(NSString *)key
{
  [[POPAnimator sharedAnimator] addAnimation:anim forObject:self key:key];
}

POPAnimator

这里关键的类是 POPAnimator
接下来我们看看这个类的结构

POPAnimator.png

类结构很简单,没有继承关系什么的。
我们知道这个类是个单例类

  • (id)sharedAnimator
    {
    static POPAnimator* _animator = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    _animator = [[POPAnimator alloc] init];
    });
    return _animator;
    }
    看看都实例化什么了
- (id)init
{
  self = [super init];
  if (nil == self) return nil;

#if TARGET_OS_IPHONE
  _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render)];
  _displayLink.paused = YES;
  [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
#else
  CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
  CVDisplayLinkSetOutputCallback(_displayLink, displayLinkCallback, (__bridge void *)self);
#endif

  _dict = POPDictionaryCreateMutableWeakPointerToStrongObject(5);
  _lock = OS_SPINLOCK_INIT;

  return self;
}

我们知道这里初始化了变量_displayLink。这里CADisplayLink相当于 vsync信号。什么是vsync信号。可以看我前面的文章垂直同步+双缓存.这里暂时_displayLink
这里并且实例化了_dict 和_lock锁


CFMutableDictionaryRef POPDictionaryCreateMutableWeakPointerToStrongObject(NSUInteger capacity)
{
  CFDictionaryKeyCallBacks kcb = kCFTypeDictionaryKeyCallBacks;

  // weak, pointer keys
  kcb.retain = NULL;
  kcb.release = NULL;
  kcb.equal = pointerEqual;
  kcb.hash = pointerHash;

  // strong, object values
  CFDictionaryValueCallBacks vcb = kCFTypeDictionaryValueCallBacks;

  return CFDictionaryCreateMutable(NULL, capacity, &kcb, &vcb);
}

这里需要注意,这个字典的值是支持hash 和equal的。


static Boolean pointerEqual(const void *ptr1, const void *ptr2) {
  return ptr1 == ptr2;
}

static CFHashCode pointerHash(const void *ptr) {
  return (CFHashCode)(ptr);
}

接下来我们看看动画如何执行的。

- (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);

  // get key, animation dict associated with object
  NSMutableDictionary *keyAnimationDict = (__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj);

  // update associated animation state
  if (nil == keyAnimationDict) {
    keyAnimationDict = [NSMutableDictionary dictionary];
    CFDictionarySetValue(_dict, (__bridge void *)obj, (__bridge void *)keyAnimationDict);
  } else {
    // if the animation instance already exists, avoid cancelling only to restart
    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;

  // create entry after potential removal
  POPAnimatorItemRef item(new POPAnimatorItem(obj, key, anim));

  // add to list and pending list
  _list.push_back(item);
  _pendingList.push_back(item);

  // support animation re-use, reset all animation state
  POPAnimationGetState(anim)->reset(true);

  // update display link
  updateDisplayLink(self);

  // unlock
  OSSpinLockUnlock(&_lock);

  // schedule runloop processing of pending animations
  [self _scheduleProcessPendingList];
}

分步分析

1 检查参数是否合法,不合法返回
2 检查是否有key ,没有key自己生成uuid作为key
3 加锁
4从_dict 字典中获取obj 对应的值keyAnimationDict。keyAnimationDict发现是个字典
5 检查obj 对应的字典是否是nil 。是nil,执行6 ,不是nil,执行7
6 生成字典,_dict 保存该字典,key 是obj
7 从keyAnimationDict 字典中查找key 对应的对象POPAnimation,如果存在对象,执行8,不存在执行9
8解锁,判断key对应的POPAnimation 和传入的参数anim是否相等,相等返回,不相等,移除对象(这里移除对象后期再分析,看源码设计好多地方)
9 让keyAnimationDict 保存anim ,key 作为在字典的key
10 创建POPAnimatorItemRef 对象(C++ 方式创建)
11 _list 添加POPAnimatorItemRef 对象(这里需要说明下_list 是c++list,可以不用分配空间,直接使用就可以了)
12_pendingList 也添加POPAnimatorItemRef 对象
13 开启动画标志位
14 检查是否需要开启刷新 这里就会执行-render 函数了
15 增加runloop 检查runloop的kCFRunLoopBeforeWaiting 和kCFRunLoopExit 状态

这里我们应该看看如何存储POPAnimation 动画的


POPAnimator数据结构

这里还需要注意的是POPAnimatorItem ,因为这里还将obj 和key POPAnimation 封装在了 POPAnimatorItem 中,保存在_list 和_pending 中

POPAnimatorItem

我们看看增加这个runloop observer干嘛了

- (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);
}

这个方法很简单,就是增加一个runloop observer,不过这个observer只观察一次就退出了。会调用_processPendingList方法

- (void)_processPendingList
{
  // rendering pending animations
  CFTimeInterval time = [self _currentRenderTime];
  [self _renderTime:(0 != _beginTime) ? _beginTime : time items:_pendingList];

  // lock
  OSSpinLockLock(&_lock);

  // clear list and observer
  _pendingList.clear();
  [self _clearPendingListObserver];

  // unlock
  OSSpinLockUnlock(&_lock);
}

1 获取下当前时间
2 执行动画
3 清空_pendingLIst
4 移除通知

- (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
  OSSpinLockLock(&_lock);

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

    // unlock
    OSSpinLockUnlock(&_lock);

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

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

  // lock
  OSSpinLockLock(&_lock);

  // update display link
  updateDisplayLink(self);

  // unlock
  OSSpinLockUnlock(&_lock);

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

1 关闭layer的隐式动画
2 通知delegate 动画将开启
3 获取_pendingList 中的数据,循环调用- (void)_renderTime:(CFTimeInterval)time item:(POPAnimatorItemRef)item,传入_pendingList 中的数据
4 要是有观察者,那么通知观察者
5 检查是否开启刷新屏幕
提交动画

下面就是核心代码了

- (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);
        }
      }
    }
  }
}

这里就是动画执行的逻辑了。

1 获取需要执行动画的obj
2 获取obj的需要执行的动画
3 获取动画的_state 。这里其实应该要仔细看看则个结构体了。
4.要是obj 是nil。执行5 ,要么执行6
5 没有对象,就将对象的动画删除掉。(先清除_dict的中的obj,并且停止动画)
6 执行动画
7 检查动画结束,执行8
8 将obj设置到相应状态
9重复次数减少
10 如果需要重复,那么就执行11,不需要执行14
11 检查动画属于POPPropertyAnimation动画,执行12,否则执行1
12.给POPPropertyAnimation 对象执行相应的回复操作
13 将state 状态回复
14 清除动画

基本逻辑完毕,总结下看到目前为止的pop的组织结构图

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容