AsyncDisplayKit源码阅读笔记

接触iOS以来,就一直对性能方面的知识颇感兴趣,但一直没(lan)时(de)间(kan)相关的知识,直到最近阅读了RunLoop相关的代码,也有了解到ASDK的异步绘制最终也是以主线程RunLoop为切入点,将绘制结果最终展示到屏幕上,所以跟着几篇文章,走了走ASDK的源码,希望能搞清楚这个库是如何高效的提升性能的。

准备工作

AsyncDisplayKit学习曲线不算平缓,官方文档有一些概念性介绍可供入门,但所谓”Talk is cheap,show me the code”,下载官方的Example再结合文档以及查阅资料,可能是上手ASDK最好的方式。

关于ASDK的几篇好的文章,可以参考:

《iOS 保持界面流畅的技巧》 - Garan no Dou

《使用 ASDK 性能调优 - 提升 iOS 界面的渲染性能》 - Draveness

《AsyncDisplayKit介绍(一)原理和思路》 - Jason Yu

《AsyncDisplayKit介绍(二)布局系统》

另外,可以科学上网的话,ASDK的成员Scott Goodson对ASDK的介绍视频也会是不错的选择。

ASDK认为,界面卡顿的主要原因是以下三点:

1.布局计算(不论是手工计算,还是利用Autolayout+Constraints,iOS系统最终会将每个view的frame计算出来,以完成布局)。

2.图形渲染(从系统默认进行的layer、view渲染,到执行自定义的drawLayerInContext、drawRect的绘制代码)。

3.UIKit对象创建/释放的时间消耗

对应的,ASDK对上述三种问题分别提出了三种解决方案(说是”一套解决方案”更为合理),即:

1.摒弃Autolayout和IB实现布局,自己实现了一套布局结局方案(ASLayout,ASLayoutSpec等概念)。

2.将图形绘制放到后台进行(这里指的将图形绘制放到后台执行,得到绘制结果后,再提交至主线程渲染到屏幕)。

3.延迟初始化UIKit对象。

本文将着重在第二点的学习上,而关于他的布局系统,你可以通过这个小游戏来入门,然后在官方Example中尝试理解- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize 这个方法的含义,最后自己动手实现目标Layout。

Getting Started

在UIKit中,我们知道,将一个UIView加入视图层级或调用[view setNeedsDisplay]后,将会触发其绘制逻辑,而出于性能考虑,系统并不会立即开始进行绘制,而仅仅是立即设置这个view为dirty,在下一次绘制事件到来时,将所有dirty的view进行重绘。

而view被加入视图层级,或是手工调用setNeedsDisplay,可以看作是UIKit绘制对象的入口,整个绘制逻辑的出发点。

所以,看ASDK如何处理原本被塞在主线程的绘制逻辑,也要从ASDK触发重绘的入口点着手。先看看ASDisplayNode+UIViewBridge扩展类中的setNeedsDisplay方法(部分截取):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)setNeedsDisplay
{
_bridge_prologue_write;
if (_hierarchyState & ASHierarchyStateRasterized) {
//当前node已被栅格化时,ASDK不再重绘这个图层,转而不断向上寻找这个图层的父图层,
} else {
BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self);
if (shouldApply) {
_messageToViewOrLayer(setNeedsDisplay);
} else {
[ASDisplayNodeGetPendingState(self) setNeedsDisplay];
}
[self __setNeedsDisplay];
}
}

主要看看Else部分的逻辑:
由于ASDK会延迟初始化view/layer,因此可能出现调用[node setNeedsDisplay]时,其对应的backingView/backingLayer尚未被初始化,而ASDisplayNodeShouldApplyBridgedWriteToView(self);的作用就是,若当前node的backingView/Layer已被初始化,且setNeedsDisplay是在主线程被调用,则简单的转发这个消息给自己的backingView/Layer,若不满足上述条件(ASDK保证node是线程安全的,所有消息都可以在非主线程调用),则在Node层设置一个标志位,标示自己需要重绘,而后进入[AsyncDisplayNode __setNeedsDisplay]

1
2
3
4
5
6
7
8
9
- (void)__setNeedsDisplay
{
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState);

if (_layer && !_flags.synchronous && nowDisplay && [self __implementsDisplay]) {
[ASDisplayNode scheduleNodeForRecursiveDisplay:self];
}
}

这里逻辑也很清晰,根据当前是否有重绘逻辑,判断是否需要重绘,解读一下这个if的判断条件:如果当前_layer为空,或是上面的nowDisplay为false时,证明当前node还未被展示到屏幕,不需要走绘制的逻辑,因为在view/layer进入对应的hierarchy时,绘制逻辑自然会跑一遍;若当前node不支持异步绘制,那么此处依然不走这个逻辑,因为此时必定是在后台线程(因为有Layer又在主线程的话,此消息已被转发给对应的backingView/Layer);最后,如果并没有override绘制逻辑或不需要栅格化子图层或不是图片展示类型的图层(如ASImageNode),则此处也不走异步绘制逻辑。

if语句判断通过,最终会跑这样两个方法:

1
- (void)_recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock

以及

1
void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)

这两个方法干的最重要的一件事就是调用了[layer displayIfNeeded]

也就是说,最终,我们的绘制逻辑会落到Layer层的display方法中,在ASDK的封装下,自然就是[_ASDisplayLayer display]中了。

别急着看绘制的代码,看完setNeedsDisplay,还要看看当node的backingView/backingLayer进入相应hierarchy时,如何触发绘制。

首先经过_个人的实验_,不论是直接实例化_ASDisplayLayer或其子类,加入到node.layer中,或者是直接对封装好的node的layer层级进行操作,如:[node.layer addSublayer: subnode.layer],都**不会得到异步绘制的优化**,从源码上看,可以看出原因:

_ASDisplayLayer.mm文件的-(void)display:(BOOL)asynchronously中我们可以看到,layer其实是将实际绘制任务交给自己的delegate完成,所以当直接实例化_ASDisplayLayer或其子类时,这个delegate是nil,自然就没有办法执行绘制逻辑。而对于第二种情况,则是因为,ASDK仅在View层上加入了对层级变化的监听,即在_ASDisplayView.mm中的- (void)willMoveToWindow:(UIWindow *)newWindow,在此处作为绘制周期的入口,而在这个函数中,调用了[ASDisplayNode __enterHierarchy]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- (void)__enterHierarchy
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"Should not cause recursive __enterHierarchy");

ASDN::MutexLocker l(_propertyLock);

if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) {
_flags.isEnteringHierarchy = YES;
_flags.isInHierarchy = YES;

if (_flags.shouldRasterizeDescendants) {
[self _recursiveWillEnterHierarchy];
} else {
[self willEnterHierarchy];
}
_flags.isEnteringHierarchy = NO;

if (self.contents == nil) {
CALayer *layer = self.layer;
[layer setNeedsDisplay];

if ([self _shouldHavePlaceholderLayer]) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
[self _setupPlaceholderLayerIfNeeded];
_placeholderLayer.opacity = 1.0;
[CATransaction commit];
[layer addSublayer:_placeholderLayer];
}
}
}
}

前半部分做的是根据情况决定是否递归设置子视图的标志位interfaceState,而末尾则调用了[layer setNeedsDisplay],来将layer设置为dirty,准备绘制。

总结一下整个ASDK的入口逻辑:

后台绘制

上文提到,经过两种不同的逻辑,最终代码会进入[_ASDisplayLayer display]并交由 [asyncDelegate displayAsyncLayer: asynchronously:];执行真正的绘制逻辑,而对于一般的_ASDisplayLayer来说,它的asyncDelegate就是ASDisplayNode,接下来把视线转移到ASDisplayNode+AsyncDisplay.mm文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
- (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously
{
ASDisplayNodeAssertMainThread();

ASDN::MutexLocker l(_propertyLock);

if (_hierarchyState & ASHierarchyStateRasterized) {
return;
}

ASSentinel *displaySentinel = (asynchronously ? _displaySentinel : nil);
int32_t displaySentinelValue = [displaySentinel increment];

//1
asdisplaynode_iscancelled_block_t isCancelledBlock = ^{
return BOOL(displaySentinelValue != displaySentinel.value);
};

//2
asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:asynchronously isCancelledBlock:isCancelledBlock rasterizing:NO];

if (!displayBlock) {
return;
}

ASDisplayNodeAssert(_layer, @"Expect _layer to be not nil");

//3
asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id<NSObject> value, BOOL canceled){
ASDisplayNodeCAssertMainThread();
if (!canceled && !isCancelledBlock()) {
NSLog(@"Displaying now!");
UIImage *image = (UIImage *)value;
BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
if (stretchable) {
ASDisplayNodeSetupLayerContentsWithResizableImage(_layer, image);
} else {
_layer.contentsScale = self.contentsScale;
_layer.contents = (id)image.CGImage;
}
[self didDisplayAsyncLayer:self.asyncLayer];
}
};

[self willDisplayAsyncLayer:self.asyncLayer];

//4
if (asynchronously) {

CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ? : _layer;

_ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction;

[transaction addOperationWithBlock:displayBlock priority:self.drawingPriority queue:[_ASDisplayLayer displayQueue] completion:completionBlock];
} else {
UIImage *contents = (UIImage *)displayBlock();
completionBlock(contents, NO);
}
}

上述代码段根据大致可以分为四段(根据注释分隔开):

1.构造isCancelledBlock,判断绘制是否被中断。

2.构造displayBlock,用于执行绘制代码,返回一个UIImage(此block就是在后台进行绘制逻辑的block)。

3.构造completionBlock,在主线程中将第二步displayBlock返回的图片赋值给layer.contents,使之在屏幕上显示。

4.若当前是异步绘制,以事务的形式管理displayBlock和completionBlock,准备后续异步绘制,否则同步绘制。

绘制

1和3的逻辑简单,这里我们先看第2步构造displayBlock的过程,因为这里涉及绘制流程,先上个简化版的代码,注意此处调用这个构造方法,传入的参数rasterizingNO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
-(asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchronous:(BOOL)asynchronous
isCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock
rasterizing:(BOOL)rasterizing
{
asyncdisplaykit_async_transaction_operation_block_t displayBlock = nil;

ASDisplayNodeAssert(rasterizing || !(_hierarchyState & ASHierarchyStateRasterized), @"Rasterized descendants should never display unless being drawn into the rasterized container.");

if (!rasterizing && self.shouldRasterizeDescendants) {
//一大堆判断
displayBlock = ^id{
//绘制,得到图片
return image;
};
} else if (_flags.implementsInstanceImageDisplay || _flags.implementsImageDisplay) {
//...
displayBlock = ^id{
//绘制,得到图片
return image;
};

} else if (_flags.implementsInstanceDrawRect || _flags.implementsDrawRect) {

//...
displayBlock = ^id{
//绘制,得到图片
return image;
};

}

return [displayBlock copy];
}


先不去看displayBlock中都是些什么玩意,这个方法主要就干一件事:根据不同的条件,构造三种不一样的displayBlock,这三种情况分别是:

1.!rasterizing && self.shouldRasterizeDescendants ,当前视图没有”正在栅格化”,并且其应该栅格化子图层,那么大致估计一下,这里的逻辑将会是让自己的子图层一个一个把自己画出来,然后自己”截图”,返回这张图片。

2._flags.implementsInstanceImageDisplay || _flags.implementsImageDisplay,当前ASDisplayNode类实现了Image绘图方法,意思是当前类是不是用以展示图片的node,譬如说ASImageNode就是这样功能的类,其需要实现:

1
- (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled

那么估计一下,这里的逻辑应该是设法绘制出图片和图层自身的视图,然后截图返回。

3._flags.implementsInstanceDrawRect || _flags.implementsDrawRect,这个参数名很直接,意思是当前类是否重写了drawRect:系列方法,如果是的话则运行drawRect:方法,并将绘制结果截图返回(这里的drawRect:是指ASDK重新定义的方法drawRect: withParameters: isCancelled: isRasterizing:)。

有了个大概的了解,再开始看每个if分支的具体逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
if (!rasterizing && self.shouldRasterizeDescendants) {
CGRect bounds = self.bounds;
if (CGRectIsEmpty(bounds)) {
return nil;
}

// Collect displayBlocks for all descendants.
NSMutableArray *displayBlocks = [NSMutableArray array];
[self _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks];

CGFloat contentsScaleForDisplay = self.contentsScaleForDisplay;
BOOL opaque = self.opaque && CGColorGetAlpha(self.backgroundColor.CGColor) == 1.0f;

ASDisplayNodeAssert(self.contentsScaleForDisplay != 0.0, @"Invalid contents scale");

displayBlock = ^id{
__ASDisplayLayerIncrementConcurrentDisplayCount(asynchronous, rasterizing);
if (isCancelledBlock()) {
__ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing);
return nil;
}

ASDN_DELAY_FOR_DISPLAY();
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);

for (dispatch_block_t block in displayBlocks) {
if (isCancelledBlock()) {
UIGraphicsEndImageContext();
__ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing);
return nil;
}
block();
}

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

__ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing);

return image;
};
}

这段主要的核心在于这一句调用:

1
[self _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks];

追踪进这个方法,会发现它处理了一些仿射变换,而后再次调用:

1
2
3
4
5
6
7
    asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:NO isCancelledBlock:isCancelledBlock rasterizing:YES];```

也就是说,这个方法间接的进行了递归,不过这次传入的参数有所不同,分别是**禁用异步**以及标志`rasterizing`为`YES`,也就是说,在`_displayBlockWithAsynchronous: isCancelledBlock:rasterizing:`方法的递归调用树中,进入这一个`if`分支的调用必然不是叶节点;而后再递减规模继续递归,获取子node的displayBlock:
```objc
for (ASDisplayNode *subnode in self.subnodes) {
[subnode _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks];
}

当递归结束时,我们回到最顶层的(我们最开始看到的那层)_displayBlockWithAsynchronous: isCancelledBlock:rasterizing:方法的第一个if分支中,此时数组displayBlocks中包含着当前node及其所有子node的绘制方法,顺序是@[当前node,子node1,子node1_1,子node2,...,子nodeN.N];,这个数组的顺序也正符合正常视图逻辑显示的顺序:superview在最底位置,而后第一个subview在倒数第二位,…,最后一个subview显示在顶部。

接下来,构造这最顶层的displayBlock,其逻辑很简单:新建一个ImageContext,遍历执行所有的displayBlock,最后从当前context中获取图片返回即可。

接下来进入第二个if分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
else if (_flags.implementsInstanceImageDisplay || _flags.implementsImageDisplay) {
// Capture drawParameters from delegate on main thread
id drawParameters = [self drawParameters];

displayBlock = ^id{
__ASDisplayLayerIncrementConcurrentDisplayCount(asynchronous, rasterizing);
if (isCancelledBlock()) {
__ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing);
return nil;
}

ASDN_DELAY_FOR_DISPLAY();

UIImage *result = nil;
//We can't call _willDisplayNodeContentWithRenderingContext or _didDisplayNodeContentWithRenderingContext because we don't
//have a context. We rely on implementors of displayWithParameters:isCancelled: to call
if (_flags.implementsInstanceImageDisplay) {
result = [self displayWithParameters:drawParameters isCancelled:isCancelledBlock];
} else {
result = [[self class] displayWithParameters:drawParameters isCancelled:isCancelledBlock];
}
__ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing);
return result;
};

}

这里的逻辑就更简单了,获取绘制参数后,把绘制的责任交给相应的子类即可,绘制参数的获取其实是调用drawParametersForAsyncLayer:方法,在ASImageNodeASTextNode中均有实现。

最后是第三个if分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
else if (_flags.implementsInstanceDrawRect || _flags.implementsDrawRect) {

CGRect bounds = self.bounds;
if (CGRectIsEmpty(bounds)) {
return nil;
}

// Capture drawParameters from delegate on main thread
id drawParameters = [self drawParameters];
CGFloat contentsScaleForDisplay = self.contentsScaleForDisplay;
BOOL opaque = self.opaque;

displayBlock = ^id{
__ASDisplayLayerIncrementConcurrentDisplayCount(asynchronous, rasterizing);
if (isCancelledBlock()) {
__ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing);
return nil;
}

ASDN_DELAY_FOR_DISPLAY();

if (!rasterizing) {
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
}

CGContextRef currentContext = UIGraphicsGetCurrentContext();
if (currentContext && _willDisplayNodeContentWithRenderingContext) {
_willDisplayNodeContentWithRenderingContext(currentContext);
}

if (_flags.implementsInstanceDrawRect) {
[self drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
} else {
[[self class] drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
}

if (currentContext && _didDisplayNodeContentWithRenderingContext) {
_didDisplayNodeContentWithRenderingContext(currentContext);
}

if (isCancelledBlock()) {
if (!rasterizing) {
UIGraphicsEndImageContext();
}
__ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing);
return nil;
}

UIImage *image = nil;
if (!rasterizing) {
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}

__ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing);

return image;
};

}

此处的逻辑也很简单,基本也是获取参数后将绘制职责转交给子类的drawRect: withParameters: isCancelled: isRasterizing:方法,需要注意的是,若当前rasterzing == YES,则不会新起一个ImageContext,而直接执行drawRect,将内容直接绘制在自己父层级的context上。

总结:

后台分发

看完displayBlock的构造过程,下一步来了解何时调用这个displayBlock进行绘制,回到ASDisplayNode+AsyncDisplay.mm- (void)displayAsyncLayer:asynchronously:方法中,重点看异步逻辑:

1
2
3
4
5
6
7
8
9
if (asynchronously) {

CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ? : _layer;

_ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction;

[transaction addOperationWithBlock:displayBlock priority:self.drawingPriority queue:[_ASDisplayLayer displayQueue] completion:completionBlock];
}

第一步是获取containerLayer,即当前layer层级中拥有transaction(事务)的最高层layer,第二步调用containerLayer.asyncdisplaykit_asyncTransaction,这个getter方法将返回一个_ASAsyncTransaction实例,这个类可以看做对GCD的一个封装,我们看看这个getter(已删除一些用于通知Observer的方法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (_ASAsyncTransaction *)asyncdisplaykit_asyncTransaction
{
_ASAsyncTransaction *transaction = self.asyncdisplaykit_currentAsyncLayerTransaction;
if (transaction == nil) {
NSHashTable *transactions = self.asyncdisplaykit_asyncLayerTransactions;
if (transactions == nil) {
transactions = [NSHashTable hashTableWithOptions:NSPointerFunctionsObjectPointerPersonality];
self.asyncdisplaykit_asyncLayerTransactions = transactions;
}
transaction = [[_ASAsyncTransaction alloc] initWithCallbackQueue:dispatch_get_main_queue() completionBlock:^(_ASAsyncTransaction *completedTransaction, BOOL cancelled) {
[transactions removeObject:completedTransaction];
//通知方法...
}];
[transactions addObject:transaction];
self.asyncdisplaykit_currentAsyncLayerTransaction = transaction;

//通知方法..
}
[[_ASAsyncTransactionGroup mainTransactionGroup] addTransactionContainer:self];
return transaction;
}

可以看到,这是一个懒创建的方法(注意,这些方法是定义在
CALayer (ASDisplayNodeAsyncTransactionContainer))扩展类中的,从这个方法中我们可以看出,在ASDK的扩展下,一个CALayer多了如下几个东西:

1.asyncdisplaykit_asyncLayerTransactions,一个哈希表,用来存_ASAsyncTransaction实例。

2.asyncdisplaykit_currentAsyncLayerTransaction,一个表示当前事务的引用。

在方法的末尾,我们将自己提交至一个事务组中:[[_ASAsyncTransactionGroup mainTransactionGroup] addTransactionContainer:self],这个东西的作用我们在后面会看到。

接着,将displayBlock和completionBlock交由这个事务管理:

1
[transaction addOperationWithBlock:displayBlock priority:self.drawingPriority queue:[_ASDisplayLayer displayQueue] completion:completionBlock];

当然,抱着好奇的心我们当然要看看这个事务怎么管理我们好不容易构造出来的block:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)addOperationWithBlock:(asyncdisplaykit_async_transaction_operation_block_t)block
priority:(NSInteger)priority
queue:(dispatch_queue_t)queue
completion:(asyncdisplaykit_async_transaction_operation_completion_block_t)completion
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(_state == ASAsyncTransactionStateOpen, @"You can only add operations to open transactions");

[self _ensureTransactionData];

ASDisplayNodeAsyncTransactionOperation *operation = [[ASDisplayNodeAsyncTransactionOperation alloc] initWithOperationCompletionBlock:completion];
[_operations addObject:operation];
_group->schedule(priority, queue, ^{
@autoreleasepool {
if (_state != ASAsyncTransactionStateCanceled) {
operation.value = block();
}
}
});
}

从代码我们可以看到,首先,我们的completionBlock被封装到一个ASDisplayNodeAsyncTransactionOperation实例中,并被保存到transaction.operations数组中,而虽然还搞不懂这个_group是什么,但是从英文直译可以看出,我们的displayBlock被schedule在某一时刻执行,执行完的结果被存放在同一个operation实例的value中,所以现在理想情况下,当我们的displayBlock执行完毕,我们应该会在主线程跑一段类似:operation.completion(operation.value);的逻辑以完成绘制流程,接下来我们来看看这个_group->schedule都干了些什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void ASAsyncTransactionQueue::GroupImpl::schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block)
{
ASAsyncTransactionQueue &q = _queue;
ASDN::MutexLocker locker(q._mutex);

DispatchEntry &entry = q._entries[queue];

//1
Operation operation;
operation._block = block;
operation._group = this;
operation._priority = priority;
entry.pushOperation(operation);

//2
++_pendingOperations; // enter group

NSUInteger maxThreads = [NSProcessInfo processInfo].activeProcessorCount * 2;

if ([[NSRunLoop mainRunLoop].currentMode isEqualToString:UITrackingRunLoopMode])
--maxThreads;

if (entry._threadCount < maxThreads) {

bool respectPriority = entry._threadCount > 0;
++entry._threadCount;
//3
dispatch_async(queue, ^{
ASDN::MutexLocker lock(q._mutex);

// go until there are no more pending operations
while (!entry._operationQueue.empty()) {
Operation operation = entry.popNextOperation(respectPriority);
{
ASDN::MutexUnlocker unlock(q._mutex);
if (operation._block) {
operation._block();
}
operation._group->leave();
operation._block = nil; // the block must be freed while mutex is unlocked
}
}
--entry._threadCount;

if (entry._threadCount == 0) {
NSCAssert(entry._operationQueue.empty() || entry._operationPriorityMap.empty(), @"No working threads but operations are still scheduled"); // this shouldn't happen
q._entries.erase(queue);
}
});
}
}

如果不看一大堆的锁机制和关于最多能起几条线程的优化逻辑,上面的逻辑大概干了这么几件事:

1.简单封装我们的block,然后operation(注意此处的operation是一个c struct,不要和上面的TransactionOperation混淆)加入对应queue的entry中。

2.++_pendingOperations,设置标志位,表示当前有多少等待执行的任务。

3.异步起一个block,这个block主要逻辑就是把要在对应queue跑的任务都给跑一遍,跑完之后调用一个_group->leave()

所以我们顺利成长的看看这个leave()又干了些什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void ASAsyncTransactionQueue::GroupImpl::leave()
{
ASDN::MutexLocker locker(_queue._mutex);
--_pendingOperations;

if (_pendingOperations == 0) {
std::list<GroupNotify> notifyList;
_notifyList.swap(notifyList);

for (GroupNotify & notify : notifyList) {
dispatch_async(notify._queue, notify._block);
}

_condition.signal();

if (_releaseCalled) {
delete this;
}
}
}

我们可以看到,在这里我们维护了_pendingOperations这个标志变量,保证他的数目准确表示当前还未完成的任务数,而在当前任务都完成后,我们取了一个GroupNotify类型的list,并且遍历并用GCD异步执行了它,这很明显是表示在任务全部完成后的一个通知方法,但目前先不用理会它通知了谁。

至此,我们已经走完了- (void)displayAsyncLayer:asynchronously:方法,我们已经成功的把displayBlock和completionBlock进行提交,并且看到displayBlock是如何被封装执行,其返回值又如何被回传至_ASDisplayNodeAsyncTransactionOperation实例中的。

现在还有一个问题:我们要在什么时候,在什么地方把执行completionBlock,从而将displayBlock执行的结果显示到屏幕上?

我们把视线移到_ASAsyncTransactionGroup.m中,在方法+ (_ASAsyncTransactionGroup *)mainTransactionGroup可以看到这个事务组注册了对主线程RunLoop的监听(还记得我们之前的Transaction就是加到这个mainGroup中的吗),其监听事件分别是kCFRunLoopBeforeWaitingkCFRunLoopExit,除去退出程序的情况,两个事件分别发生在RunLoop休眠前以及切换RunLoopMode状态时,接下来看看注册的回调:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)commit
{
ASDisplayNodeAssertMainThread();

if ([_containerLayers count]) {
NSHashTable *containerLayersToCommit = _containerLayers;
_containerLayers = [NSHashTable hashTableWithOptions:NSPointerFunctionsObjectPointerPersonality];

for (CALayer *containerLayer in containerLayersToCommit) {

_ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_currentAsyncLayerTransaction;
containerLayer.asyncdisplaykit_currentAsyncLayerTransaction = nil;
[transaction commit];
}
}
}

事务组的commit,其实就是遍历其中所有的事务,执行事务的commit,所以我们再看看事务的commit,不过这之前,我们理清楚一下我们的completionBlock是如何存储在这个事务中的:

接下来是_ASAsyncTransactioncommit方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)commit
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(_state == ASAsyncTransactionStateOpen, @"You cannot double-commit a transaction");
_state = ASAsyncTransactionStateCommitted;

if ([_operations count] == 0) {
if (_completionBlock) {
_completionBlock(self, NO);
}
} else {
ASDisplayNodeAssert(_group != NULL, @"If there are operations, dispatch group should have been created");

_group->notify(_callbackQueue, ^{

ASDisplayNodeAssertMainThread();
[self completeTransaction];
});
}
}

之所以要先说清楚我们的completionBlock是如何存储的,是因为,每个_ASAsyncTransaction事务本身也有一个completionBlock,这里不要和我们绘图的completionBlock弄混,这里的重点在else分支,我们可以看到了一个陌生又熟悉的玩意:_group->notify,我们在追踪进去之前,先看看参数:我们传入了一个_callbackQueue,如果你还记得上文贴过代码的- (_ASAsyncTransaction *)asyncdisplaykit_asyncTransaction方法,你会发现这里的_callbakcQueue就是主线程,而后我们传入了一个block,执行一个completeTransaction方法。

接下来看看这个_group->notify做了些啥:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void ASAsyncTransactionQueue::GroupImpl::notify(dispatch_queue_t queue, dispatch_block_t block)
{
ASDN::MutexLocker locker(_queue._mutex);

if (_pendingOperations == 0) {
dispatch_async(queue, block);
} else {
GroupNotify notify;
notify._block = block;
notify._queue = queue;
_notifyList.push_back(notify);
}
}

嘿!看到这里你应该想到了上文的_group->leave()方法,看来leave()中,我们通知的对象正是我们在此处注册的一个回调!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void ASAsyncTransactionQueue::GroupImpl::leave()
{
ASDN::MutexLocker locker(_queue._mutex);
--_pendingOperations;

if (_pendingOperations == 0) {
std::list<GroupNotify> notifyList;
_notifyList.swap(notifyList);

for (GroupNotify & notify : notifyList) {
dispatch_async(notify._queue, notify._block);
}

_condition.signal();

// there was attempt to release the group before, but we still
// had operations scheduled so now is good time
if (_releaseCalled) {
delete this;
}
}
}

当绘制任务全部完成后,我们传入的block将在指定的队列执行!在此处,自然就是在主线程跑completeTransaction方法啦,那我们接下来继续追踪这个方法,发现其中除了执行_ASAsyncTransaction自己的completionBlock之外,还有这么一句调用:

1
2
3
for (ASDisplayNodeAsyncTransactionOperation *operation in _operations) {
[operation callAndReleaseCompletionBlock:isCanceled];
}

还记得_ASAsyncTransaction是怎么存我们的completionBlock的吗?我们再看看这个函数干了啥:

1
2
3
4
5
6
7
8
9
- (void)callAndReleaseCompletionBlock:(BOOL)canceled;
{
if (_operationCompletionBlock) {
_operationCompletionBlock(self.value, canceled);
// Guarantee that _operationCompletionBlock is released on _callbackQueue:
self.operationCompletionBlock = nil;
}
}

Bingo!在此处,我们终于将displayBlock所返回的值交由completionBlock,让它在主线程展示我们绘制的结果!至此,ASDK后台绘制的流程也就全部结束啦!

总结:

总结

至此,有关ASDK的异步绘制源码已经大致走了一遍了,总的来说:

ASDK的绘制流程被触发后,会一方面监听MainRunLoop的事件,一方面尽快开始异步的绘制(绘制的逻辑是:递归获取整个NodeTree(ViewTree/LayerTree)的绘制代码,并在一个上下文中绘制并截图),同时在MainRunLoop运行到”合适”的时机,将后台绘制的结果简单的赋值给layer.contents,完成界面的展示。

_个人实验_证明,当后台绘制耗时很长,而RunLoop的ObserveCallback通知在绘制完成前结束、RunLoop进入休眠模式后,由于绘制结束后的notify最终会在mainQueue中执行layer.contents = image的逻辑,因此RunLoop会因为需要操作主线程事务而被唤醒,以__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__的方式执行赋值操作,有关于RunLoop的相关知识,可以看我的这篇文章