Farlanki

AsyncDisplayKit(一)初识

字数统计: 3.7k阅读时长: 18 min
2017/01/10 Share

AsyncDisplayKit介绍

AsyncDisplayKit的是Facebbook为了解决其App在某些机型上的性能问题而写的一个库。这个库使用ASNode把UIView再进行了封装,最大限度的保留开发者对于使用UIView的习惯。在UIView之上的ASNode是线程安全的,即使在后台线程使用ASNode也毫无问题。

这篇文章分析的主要是ASNode的绘制流程。

关于CALayer的绘制时机

在分析ASNode的绘制流程之前,我们先来了解一下一些关于CALayer的知识。当一个CALayer需要更新的时候,系统会先将其标记,在下一个更新周期调用其-display()方法对其进行绘制。ASDK也参考了这个流程,其_ASDisplayLayer中的-(void)display方法可以作为我们分析ASNode的绘制流程的入口。

进入绘制流程

ASDisplayNode主要通过两个方式来进入绘制流程。

  • 当一个ASDisplayNode-setNeedsDisplay方法被调用后。
  • 当一个node被作为参数通过-addSubnode:加入到parent view中时。实现在UIView(AsyncDisplayKit)中)。当一个node被加入到view中,实际上是node中的view被加入到parent view中。这时-willMoveToWindow:方法就被被调用。
    上述两个流程最终都会调用_ASDisplayLayer-(void)display方法。

    (void)display:(BOOL)asynchronously

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     - (void)display:(BOOL)asynchronously
    {
    if (CGRectIsEmpty(self.bounds)) {
    _attemptedDisplayWhileZeroSized = YES;
    }

    id<_ASDisplayLayerDelegate> NS_VALID_UNTIL_END_OF_SCOPE strongAsyncDelegate;
    {
    _asyncDelegateLock.lock();
    strongAsyncDelegate = _asyncDelegate;
    _asyncDelegateLock.unlock();
    }

    [strongAsyncDelegate displayAsyncLayer:self asynchronously:asynchronously];
    }

    -(void)display:(BOOL)asynchronously这个方法中调用了_asyncDelegate- (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously方法。在ASDK的实现中,_ASDisplayLayer的_asyncDelegate为ASDisplayNode。

    - (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously

    接下来进入-displayAsyncLayer:asynchronously

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
- (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously
{
ASDisplayNodeAssertMainThread();

ASDN::MutexLocker l(__instanceLock__);

if (_hierarchyState & ASHierarchyStateRasterized) {
return;
}

// for async display, capture the current displaySentinel value to bail early when the job is executed if another is
// enqueued
// for sync display, do not support cancellation

// FIXME: what about the degenerate case where we are calling setNeedsDisplay faster than the jobs are dequeuing
// from the displayQueue? Need to not cancel early fails from displaySentinel changes.
asdisplaynode_iscancelled_block_t isCancelledBlock = nil;
if (asynchronously) {
uint displaySentinelValue = ++_displaySentinel;
__weak ASDisplayNode *weakSelf = self;
isCancelledBlock = ^BOOL{
__strong ASDisplayNode *self = weakSelf;
return self == nil || (displaySentinelValue != self->_displaySentinel.load());
};
} else {
isCancelledBlock = ^BOOL{
return NO;
};
}

// Set up displayBlock to call either display or draw on the delegate and return a UIImage contents
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");

// This block is called back on the main thread after rendering at the completion of the current async transaction, or immediately if !asynchronously
asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id<NSObject> value, BOOL canceled){
ASDisplayNodeCAssertMainThread();
if (!canceled && !isCancelledBlock()) {
UIImage *image = (UIImage *)value;
BOOL stretchable = (NO == UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero));
if (stretchable) {
ASDisplayNodeSetupLayerContentsWithResizableImage(_layer, image);
} else {
_layer.contentsScale = self.contentsScale;
_layer.contents = (id)image.CGImage;
}
[self didDisplayAsyncLayer:self.asyncLayer];
}
};

// Call willDisplay immediately in either case
[self willDisplayAsyncLayer:self.asyncLayer asynchronously:asynchronously];

if (asynchronously) {
// Async rendering operations are contained by a transaction, which allows them to proceed and concurrently
// while synchronizing the final application of the results to the layer's contents property (completionBlock).

// First, look to see if we are expected to join a parent's transaction container.
CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ? : _layer;

// In the case that a transaction does not yet exist (such as for an individual node outside of a container),
// this call will allocate the transaction and add it to _ASAsyncTransactionGroup.
// It will automatically commit the transaction at the end of the runloop.
_ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction;

// Adding this displayBlock operation to the transaction will start it IMMEDIATELY.
// The only function of the transaction commit is to gate the calling of the completionBlock.
[transaction addOperationWithBlock:displayBlock priority:self.drawingPriority queue:[_ASDisplayLayer displayQueue] completion:completionBlock];
} else {
UIImage *contents = (UIImage *)displayBlock();
completionBlock(contents, NO);
}
}

现在来看一下这个方法都干了些啥。

  • 判断layer是否处于一个已经格栅化的层级中(是否有一个父node被设置了.shouldRasterizeDescendants),如果是,则返回(因为格栅化要从被标记了格栅化的根node开始)
  • 构造isCancelBlock。
  • 构造调用方法displayBlock,isCancelBlock作为参数。
  • 如果方法返回nil,直接返回。()
  • 构造completionBlock。
  • 从视图层次中最底端的layer中取得一个__ASAsyncTransaction。
  • 将构造completionBlock和displayBlock加入transaction中,并指定优先级和分发的队列(主队列)。

其中,displayBlock负责的是将每个layer的content计算出来,而completionBlock的工作便是将计算出来的内容赋值到layer.content中。
现在来看看构造displayBlock的方法.

-_displayBlockWithAsynchronous:isCancelledBlock:rasterizing

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
asyncdisplaykit_async_transaction_operation_block_t displayBlock = nil;
ASDisplayNodeFlags flags;

__instanceLock__.lock();

flags = _flags;

// We always create a graphics context, unless a -display method is used, OR if we are a subnode drawing into a rasterized parent.
BOOL shouldCreateGraphicsContext = (flags.implementsInstanceImageDisplay == NO && flags.implementsImageDisplay == NO && rasterizing == NO);
BOOL shouldBeginRasterizing = (rasterizing == NO && flags.shouldRasterizeDescendants);
BOOL usesInstanceMethodDisplay = (flags.implementsInstanceDrawRect || flags.implementsInstanceImageDisplay);
BOOL usesImageDisplay = (flags.implementsImageDisplay || flags.implementsInstanceImageDisplay);
BOOL usesDrawRect = (flags.implementsDrawRect || flags.implementsInstanceDrawRect);

if (usesImageDisplay == NO && usesDrawRect == NO && shouldBeginRasterizing == NO) {
// Early exit before requesting more expensive properties like bounds and opaque from the layer.
__instanceLock__.unlock();
return nil;
}

BOOL opaque = self.opaque;
CGRect bounds = self.bounds;
CGFloat contentsScaleForDisplay = _contentsScaleForDisplay;

// Capture drawParameters from delegate on main thread, if this node is displaying itself rather than recursively rasterizing.
id drawParameters = (shouldBeginRasterizing == NO ? [self drawParameters] : nil);

__instanceLock__.unlock();

// Only the -display methods should be called if we can't size the graphics buffer to use.
if (CGRectIsEmpty(bounds) && (shouldBeginRasterizing || shouldCreateGraphicsContext)) {
return nil;
}

ASDisplayNodeAssert(contentsScaleForDisplay != 0.0, @"Invalid contents scale");
ASDisplayNodeAssert(usesInstanceMethodDisplay == NO || (flags.implementsDrawRect == NO && flags.implementsImageDisplay == NO),
@"Node %@ should not implement both class and instance method display or draw", self);
ASDisplayNodeAssert(rasterizing || !(_hierarchyState & ASHierarchyStateRasterized),
@"Rasterized descendants should never display unless being drawn into the rasterized container.");

if (shouldBeginRasterizing) {
// Collect displayBlocks for all descendants.
NSMutableArray *displayBlocks = [NSMutableArray array];
[self _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks];
CHECK_CANCELLED_AND_RETURN_NIL();

// If [UIColor clearColor] or another semitransparent background color is used, include alpha channel when rasterizing.
// Unlike CALayer drawing, we include the backgroundColor as a base during rasterization.
opaque = opaque && CGColorGetAlpha(self.backgroundColor.CGColor) == 1.0f;

displayBlock = ^id{
CHECK_CANCELLED_AND_RETURN_NIL();

UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);

for (dispatch_block_t block in displayBlocks) {
CHECK_CANCELLED_AND_RETURN_NIL(UIGraphicsEndImageContext());
block();
}

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

ASDN_DELAY_FOR_DISPLAY();
return image;
};
} else {
displayBlock = ^id{
CHECK_CANCELLED_AND_RETURN_NIL();

if (shouldCreateGraphicsContext) {
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
CHECK_CANCELLED_AND_RETURN_NIL( UIGraphicsEndImageContext(); );
}

CGContextRef currentContext = UIGraphicsGetCurrentContext();
UIImage *image = nil;

// For -display methods, we don't have a context, and thus will not call the _willDisplayNodeContentWithRenderingContext or
// _didDisplayNodeContentWithRenderingContext blocks. It's up to the implementation of -display... to do what it needs.
if (currentContext && _willDisplayNodeContentWithRenderingContext) {
_willDisplayNodeContentWithRenderingContext(currentContext);
}

// Decide if we use a class or instance method to draw or display.
id object = usesInstanceMethodDisplay ? self : [self class];

if (usesImageDisplay) { // If we are using a display method, we'll get an image back directly.
image = [object displayWithParameters:drawParameters
isCancelled:isCancelledBlock];
} else if (usesDrawRect) { // If we're using a draw method, this will operate on the currentContext.
[object drawRect:bounds withParameters:drawParameters
isCancelled:isCancelledBlock isRasterizing:rasterizing];
}

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

if (shouldCreateGraphicsContext) {
CHECK_CANCELLED_AND_RETURN_NIL( UIGraphicsEndImageContext(); );
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}

ASDN_DELAY_FOR_DISPLAY();
return image;
};
}

现在来看看这个方法又干了什么

  • 如果一个node没有实现drawRect:withParameters:isCancelled:isRasterizing:方法,没有实现displayWithParameters:isCancelled:方法和并不是需要光栅化的层级的根Node的话,返回.
  • 如果一个node的Rect为零的话,返回
  • 如果当前节点为视图层次结构的根节点并且被标记为shouldRasterizeDescendants的话
    • 构造一个displayBlocks的数组,调用-__recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:displayBlocks:的方法得到的block都存在这个数组中.
    • 构造一个displayBlock,负责遍历刚才得到的displayBlocks数组.取得全部block运行后得到的结果(一个UIImage),并返回这个image.
  • 如果当前节点实现了上述的display方法或者draw 方法,那么新建一个displayBlock,负责调用这两个方法,最后返回一个UIImage对象.

可以看到,这个方法主要是根据不用的情况构造diaplayBlock.node实现了display方法或者实现了drawRect方法的情况比较简单.下面主要来看看在光栅化的情况下-__recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:displayBlocks:方法做了些什么.

-__recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:displayBlocks:

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
- (void)_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock displayBlocks:(NSMutableArray *)displayBlocks
{
// Skip subtrees that are hidden or zero alpha.
if (self.isHidden || self.alpha <= 0.0) {
return;
}

BOOL rasterizingFromAscendent = (_hierarchyState & ASHierarchyStateRasterized);

// if super node is rasterizing descendants, subnodes will not have had layout calls because they don't have layers
if (rasterizingFromAscendent) {
[self __layout];
}

// Capture these outside the display block so they are retained.
UIColor *backgroundColor = self.backgroundColor;
CGRect bounds = self.bounds;
CGFloat cornerRadius = self.cornerRadius;
BOOL clipsToBounds = self.clipsToBounds;

CGRect frame;

// If this is the root container node, use a frame with a zero origin to draw into. If not, calculate the correct frame using the node's position, transform and anchorPoint.
if (self.shouldRasterizeDescendants) {
frame = CGRectMake(0.0f, 0.0f, bounds.size.width, bounds.size.height);
} else {
CGPoint position = self.position;
CGPoint anchorPoint = self.anchorPoint;

// Pretty hacky since full 3D transforms aren't actually supported, but attempt to compute the transformed frame of this node so that we can composite it into approximately the right spot.
CGAffineTransform transform = CATransform3DGetAffineTransform(self.transform);
CGSize scaledBoundsSize = CGSizeApplyAffineTransform(bounds.size, transform);
CGPoint origin = CGPointMake(position.x - scaledBoundsSize.width * anchorPoint.x,
position.y - scaledBoundsSize.height * anchorPoint.y);
frame = CGRectMake(origin.x, origin.y, bounds.size.width, bounds.size.height);
}

// Get the display block for this node.
asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:NO isCancelledBlock:isCancelledBlock rasterizing:YES];

// We'll display something if there is a display block, clipping, translation and/or a background color.
BOOL shouldDisplay = displayBlock || backgroundColor || CGPointEqualToPoint(CGPointZero, frame.origin) == NO || clipsToBounds;

// If we should display, then push a transform, draw the background color, and draw the contents.
// The transform is popped in a block added after the recursion into subnodes.
if (shouldDisplay) {
dispatch_block_t pushAndDisplayBlock = ^{
// Push transform relative to parent.
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);

CGContextTranslateCTM(context, frame.origin.x, frame.origin.y);

//support cornerRadius
if (rasterizingFromAscendent && clipsToBounds) {
if (cornerRadius) {
[[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius] addClip];
} else {
CGContextClipToRect(context, bounds);
}
}

// Fill background if any.
CGColorRef backgroundCGColor = backgroundColor.CGColor;
if (backgroundColor && CGColorGetAlpha(backgroundCGColor) > 0.0) {
CGContextSetFillColorWithColor(context, backgroundCGColor);
CGContextFillRect(context, bounds);
}

// If there is a display block, call it to get the image, then copy the image into the current context (which is the rasterized container's backing store).
if (displayBlock) {
UIImage *image = (UIImage *)displayBlock();
if (image) {
BOOL opaque = ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage));
CGBlendMode blendMode = opaque ? kCGBlendModeCopy : kCGBlendModeNormal;
[image drawInRect:bounds blendMode:blendMode alpha:1];
}
}
};
[displayBlocks addObject:pushAndDisplayBlock];
}

// Recursively capture displayBlocks for all descendants.
for (ASDisplayNode *subnode in self.subnodes) {
[subnode _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks];
}

下面来看看这个方法做了些什么

  • 如果不是根node,先进行仿射变换,符合设置的bounds.
  • 再次调用_displayBlockWithAsynchronous:isCancelledBlock:rasterizing:取得displayBlock.注意这次调用此方法和上次调用的方法不同,rasterizing被设置成了yes.这样,当调用_displayBlockWithAsynchronous:isCancelledBlock:rasterizing:方法的时候,不会再进入这个方法,而是直接取得调用display方法或者drawRect方法的displayBlock.
  • 将取得的displayBlock封装成pushAndDisplayBlock,这个block负责调用displayBlock,在context中绘制图片.
  • pushAndDisplayBlock加入数组.
  • 对每个子节点调用_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:displayBlocks:.这样其实是进行递归调用.当子节点调用_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:displayBlocks:时,会再回到-_displayBlockWithAsynchronous:isCancelledBlock:rasterizing,取得调用display或者drawRect方法的displayBlock,再取得pushAndDisplayBlock,加入数组

总结:这个方法其实是取得一系列可以在context中将本layer中的内容画出来的block.当这个方法调用完毕后,根node将会取得一个block数组,遍历这个数组,就可以在context中把以其为根节点的整个视图层级的内容绘制出来,返回一个UIImage.

- (void)addOperationWithBlock:priority:queue:completion:

在displayBlock和completion构造完成后,就要看看到底什么时候调用displayBlock和completion了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (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
{
ASAsyncTransactionAssertMainThread();
NSAssert(self.state == ASAsyncTransactionStateOpen, @"You can only add operations to open transactions");

[self _ensureTransactionData];

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

先看看_ASAsyncTransaction的结构是怎样的:

1
2
3
4
5
6
@implementation _ASAsyncTransaction
{
ASAsyncTransactionQueue::Group *_group;
NSMutableArray<ASAsyncTransactionOperation *> *_operations;
_Atomic(ASAsyncTransactionState) _state;
}

这个方法做了以下几件事:

  • 以completion为参数构造ASAsyncTransactionOperation加入到_ASAsyncTransaction对象的_operations数组中.
  • 构造一个执行display的block,将结果存放在ASAsyncTransactionOperation的value中.group中调度该block.

接下来看看_group的schedule究竟是怎样操作的

void ASAsyncTransactionQueue::GroupImpl::schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block)

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
void ASAsyncTransactionQueue::GroupImpl::schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block)
{
ASAsyncTransactionQueue &q = _queue;
std::lock_guard<std::mutex> l(q._mutex);

DispatchEntry &entry = q._entries[queue];

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

++_pendingOperations; // enter group

#if ASDISPLAYNODE_DELAY_DISPLAY
NSUInteger maxThreads = 1;
#else
NSUInteger maxThreads = [NSProcessInfo processInfo].activeProcessorCount * 2;

// Bit questionable maybe - we can give main thread more CPU time during tracking;
if ([[NSRunLoop mainRunLoop].currentMode isEqualToString:UITrackingRunLoopMode])
--maxThreads;
#endif

if (entry._threadCount < maxThreads) { // we need to spawn another thread

// first thread will take operations in queue order (regardless of priority), other threads will respect priority
bool respectPriority = entry._threadCount > 0;
++entry._threadCount;

dispatch_async(queue, ^{
std::unique_lock<std::mutex> lock(q._mutex);

// go until there are no more pending operations
while (!entry._operationQueue.empty()) {
Operation operation = entry.popNextOperation(respectPriority);
lock.unlock();
if (operation._block) {
ASProfilingSignpostStart(3, operation._block);
operation._block();
ASProfilingSignpostEnd(3, operation._block);
}
operation._group->leave();
operation._block = nil; // the block must be freed while mutex is unlocked
lock.lock();
}
--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);
}
});
}
}

可以看到,schedule方法管理着线程数,并且一直取出operation运行,直到所有operation运行完毕,调用leave方法.有点像GCD的dispatch_group_t.
接下来看看leave方法.

void ASAsyncTransactionQueue::GroupImpl::leave()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void ASAsyncTransactionQueue::GroupImpl::leave()
{
std::lock_guard<std::mutex> l(_queue._mutex);
--_pendingOperations;

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

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

_condition.notify_one();

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

对在notifyList每一个notify,调用notify._block.其实这里就像dispatch_group_t中的那样,当一个group完成后,发送一个通知.那么这个通知发送到哪里呢?换句话说,什么时候向notifyList添加notify呢?

修改content

答案是第一次取得mainTransactionGroup的时候.mainTransactionGroup采用惰性初始化,在首次取得mainTransactionGroup的时候,程序会在runloop中注册一个回调,回调之后会调用一个commit方法.

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
- (void)commit
{
ASAsyncTransactionAssertMainThread();
NSAssert(self.state == ASAsyncTransactionStateOpen, @"You cannot double-commit a transaction");
self.state = ASAsyncTransactionStateCommitted;

if ([_operations count] == 0) {
// Fast path: if a transaction was opened, but no operations were added, execute completion block synchronously.
if (_completionBlock) {
_completionBlock(self, NO);
}
} else {
NSAssert(_group != NULL, @"If there are operations, dispatch group should have been created");

_group->notify(_callbackQueue, ^{
// _callbackQueue is the main queue in current practice (also asserted in -waitUntilComplete).
// This code should be reviewed before taking on significantly different use cases.
ASAsyncTransactionAssertMainThread();
[self completeTransaction];
});
}
}

void ASAsyncTransactionQueue::GroupImpl::notify(dispatch_queue_t queue, dispatch_block_t block)
{
std::lock_guard<std::mutex> l(_queue._mutex);

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

transaction已经完成的情况

在上述notify方法中,如果_pendingOperations为0,则代表已经完成了这个transaction.那样就可以在主线程中进行赋值等工作,不需要注册notify.

transcation未完成的情况

在上述notify方法中,如果_pendingOperations不为0,则表示transaction中的operation还没有全部完成.则需要注册一个notify,并储存在notifyList中.等待全部operation完成后后再在主线程中进行赋值工作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)completeTransaction
{
ASAsyncTransactionState state = self.state;
if (state != ASAsyncTransactionStateComplete) {
BOOL isCanceled = (state == ASAsyncTransactionStateCanceled);
for (ASAsyncTransactionOperation *operation in _operations) {
[operation callAndReleaseCompletionBlock:isCanceled];
}

// Always set state to Complete, even if we were cancelled, to block any extraneous
// calls to this method that may have been scheduled for the next runloop
// (e.g. if we needed to force one in this runloop with -waitUntilComplete, but another was already scheduled)
self.state = ASAsyncTransactionStateComplete;

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

就是在这里以value作为参数调用了completion.

关于dispatch_async和runloop的一点细节

如果以主线程为参数调用dispatch_async,block会在下一个runloop被执行.所以如果在notify被调用前所有operation已经完成,那么被改变的layer会在下一个runloop中被更新.如果operation在notify被调用时还没有完全被完成,那么layer将会在operation全部完成的那一刻的下一个runloop中被更新.即layer总是在根node的所有operation都完成后的下一个runloop中被更新.

总结

在ASDK的绘制流程被触发后,负责调度block的mainTransactionGroup会在本次runloop结束前注册一个通知,另一方面,取得需要显示的node的block并且在后台运行这些block,取得结果,在所有工作完成后通知被注册方,执行completion,在completion中将block的结果赋值给各个layer的content,这样就完成了ASDK的绘制流程.

CATALOG
  1. 1. AsyncDisplayKit介绍
  2. 2. 关于CALayer的绘制时机
  3. 3. 进入绘制流程
    1. 3.1. (void)display:(BOOL)asynchronously
    2. 3.2. - (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously
    3. 3.3. -_displayBlockWithAsynchronous:isCancelledBlock:rasterizing
    4. 3.4. -__recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:displayBlocks:
    5. 3.5. - (void)addOperationWithBlock:priority:queue:completion:
    6. 3.6. void ASAsyncTransactionQueue::GroupImpl::schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block)
    7. 3.7. void ASAsyncTransactionQueue::GroupImpl::leave()
  4. 4. 修改content
    1. 4.1. transaction已经完成的情况
    2. 4.2. transcation未完成的情况
  5. 5. 关于dispatch_async和runloop的一点细节
  6. 6. 总结