一、前言
上一節講解了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中的方法,我們先暫時放著,先探討下里面兩個比較重要的方法:applyAnimationTime
和applyAnimationToValue
d. applyAnimationTime
和applyAnimationToValue
方法
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)];
}
}
}
}
複製程式碼
上面的程式碼看似比較繁瑣,但其關鍵之處在於POPAnimatablePropertyReadBlock
和POPAnimatablePropertyWriteBlock
兩者。
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來說明。