1. Texture 异步绘制源码分析
1.1 整体流程图

在开始分析代码之前先给大家看下整个异步绘制的关键流程图,我把它划分成两条主线,第一条以setNeedDisplay为起点,将displayBlock返回的UImage在Group中转换为ASAsyncTransationOperation的value,另一条是以
RunLoop休眠前及退出RunLoop事件为触发点,将第一条主线产生的UImage通过ASAsyncTransationOperation 的 operationCompleteBlock 传到layer.content.

整个图可以分成两大块,第一块是displayBlock生成UImage,第二块是UImage怎么传到layer.content

围绕着这两条主线,两大块就可以捋清楚整个异步绘制的整个流程。

1.2 触发异步绘制的入口点

Texture触发异步绘制的入口点主要有如下两个地方:

  • UIView加入视图层级,这时候会调用willMoveToWindow
  • 直接调用setNeedsDisplay方法

其实最终都会归并到setNeedsDisplay

首先看下willMoveToWindow,这里会调用ASDisplayNode的__enterHierarchy

- (void)willMoveToWindow:(UIWindow *)newWindow {
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
BOOL visible = (newWindow != nil);
if (visible && !node.inHierarchy) {
[node __enterHierarchy];
}
}

在__enterHierarchy中会调用子节点的__enterHierarchy,并且根据具体情况创建placeHolderLayer添加到当前layer上

- (void)__enterHierarchy
{
ASDisplayNodeAssertMainThread();
//.......

if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) {
//.......
//更新状态
[self willEnterHierarchy];
//遍历节点调用节点的__enterHierarchy
for (ASDisplayNode *subnode in self.subnodes) {
[subnode __enterHierarchy];
}
//.......
if (self.contents == nil) { //如果内容为空
CALayer *layer = self.layer;
[layer setNeedsDisplay]; //调用layer的setNeedsDisplay
//如果有占位图则显示占位图
if ([self _locked_shouldHavePlaceholderLayer]) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
//显示placeHolderLayer
[self _locked_setupPlaceholderLayerIfNeeded];
_placeholderLayer.opacity = 1.0;
[CATransaction commit];
//将placehHolderLayer添加到layer
[layer addSublayer:_placeholderLayer];
}
}
}
[self didEnterHierarchy];
}

这里比较关键的是setNeedsDisplay,在这里会创建后续需要的渲染队列,渲染队列是一个高优先级的串行异步队列,displayBlock就是这个队列中执行的,

- (void)setNeedsDisplay
{
//...........
{
//...........
if (isRasterized == NO && shouldApply == NO) {
// We can't release the lock before applying to pending state, or it may be flushed before it can be applied.
[ASDisplayNodeGetPendingState(self) setNeedsDisplay];
}
}

if (isRasterized) {
ASPerformBlockOnMainThread(^{
// The below operation must be performed on the main thread to ensure against an extremely rare deadlock, where a parent node
// begins materializing the view / layer hierarchy (locking itself or a descendant) while this node walks up
// the tree and requires locking that node to access .rasterizesSubtree.
// For this reason, this method should be avoided when possible. Use _hierarchyState & ASHierarchyStateRasterized.
ASDisplayNodeAssertMainThread();
ASDisplayNode *rasterizedContainerNode = self.supernode;
while (rasterizedContainerNode) {
if (rasterizedContainerNode.rasterizesSubtree) {
break;
}
rasterizedContainerNode = rasterizedContainerNode.supernode;
}
//将根节点标记为dirty
[rasterizedContainerNode setNeedsDisplay];
});
} else {
if (shouldApply) {
// If not rasterized, and the node is loaded (meaning we certainly have a view or layer), send a
// message to the view/layer first. This is because __setNeedsDisplay calls as scheduleNodeForDisplay,
// which may call -displayIfNeeded. We want to ensure the needsDisplay flag is set now, and then cleared.
[viewOrLayer setNeedsDisplay];
}
[self __setNeedsDisplay];
}
}
- (void)__setNeedsDisplay
{
BOOL shouldScheduleForDisplay = NO;
{
MutexLocker l(__instanceLock__);
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState);
// FIXME: This should not need to recursively display, so create a non-recursive variant.
// The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive.
if (_layer != nil && !checkFlag(Synchronous) && nowDisplay && [self _implementsDisplay]) {
shouldScheduleForDisplay = YES;
}
}

if (shouldScheduleForDisplay) {
[ASDisplayNode scheduleNodeForRecursiveDisplay:self];
}
}

+ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node
{
static dispatch_once_t onceToken;
static ASRunLoopQueue<ASDisplayNode *> *renderQueue;
dispatch_once(&onceToken, ^{
renderQueue = [[ASRunLoopQueue<ASDisplayNode *> alloc] initWithRunLoop:CFRunLoopGetMain()
retainObjects:NO
handler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) {
[dequeuedItem _recursivelyTriggerDisplayAndBlock:NO];
if (isQueueDrained) {
CFTimeInterval timestamp = CACurrentMediaTime();
[[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification
object:nil
userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: @(timestamp)}];
}
}];
});

as_log_verbose(ASDisplayLog(), "%s %@", sel_getName(_cmd), node);
[renderQueue enqueue:node];
}

经过层层调用会调到_ASDisplayLayer的display方法

- (void)display
{
ASDisplayNodeAssertMainThread();
[self _hackResetNeedsDisplay];

if (self.displaySuspended) {
return;
}

[self display:self.displaysAsynchronously];
}

display方法中的实际工作就是调用 - (void)display:(BOOL)asynchronously,在- (void)display:(BOOL)asynchronously中将流程转到asyncDelegate中

- (void)display:(BOOL)asynchronously
{
if (CGRectIsEmpty(self.bounds)) {
_attemptedDisplayWhileZeroSized = YES;
}
[self.asyncDelegate displayAsyncLayer:self asynchronously:asynchronously];
}
1.3 几个重要类之间的关系

接下来就是比较重要的异步绘制的流程了,这里主要分两大块:

  1. displayBlock,completionBlock 指责是什么,怎么构建出来的,在什么时机调用?
  2. 整个异步绘制的流程是怎样的?

对于第一点我们先留一个印象,displayBlock是用于生成UImage的,completionBlock是用于将displayBlock获得的UIImage设置到layer.content中。我们先把最大块的第二点给梳理清楚—整个异步绘制的流程。

在开始之前需先梳理下_ASAsyncTransactionGroup,ASAsyncTransactionContainer,_ASAsyncTransaction,ASAsyncTransactionQueue,ASAsyncTransactionOperation,DispatchEntry,Operation 这些类的关系,其实梳理了这些类的关系整个异步绘制流程就会显得十分清晰,建议大家可以结合文章开始时候给出的图来看。

  • _ASAsyncTransactionGroup

_ASAsyncTransactionGroup 用于管理ASAsyncTransactionContainer,ASAsyncTransactionContainer通过_ASAsyncTransactionGroup的addTransactionContainer方法添加到_ASAsyncTransactionGroup中,这里有个很重要的_ASAsyncTransactionGroup 就是mainTransactionGroup,在每个主线程RunLoop即将进入睡眠期间,以及由于RunLoop模式切换等原因导致的当前RunLoop退出的时候将添加mainTransactionGroup中ASAsyncTransactionContainer中的_ASAsyncTransaction 调用commit进行提交,这里先卖个关子,先不介绍commit到底是干啥的。我们先看看mainTransactionGroup:


+ (_ASAsyncTransactionGroup *)mainTransactionGroup
{
ASDisplayNodeAssertMainThread();
static _ASAsyncTransactionGroup *mainTransactionGroup;
//懒加载_ASAsyncTransactionGroup
if (mainTransactionGroup == nil) {
mainTransactionGroup = [[_ASAsyncTransactionGroup alloc] _init];
//注册监听runloop结束或者进入休眠之前的通知
[mainTransactionGroup registerAsMainRunloopObserver];
}
return mainTransactionGroup;
}

mainTransactionGroup 是以懒加载的方式实例化出来,在创建的时候会注册监听kCFRunLoopBeforeWaiting和kCFRunLoopExit这两个通知,在RunLoop睡眠和退出之前,在主线程调用commit。

- (void)registerAsMainRunloopObserver
{
ASDisplayNodeAssertMainThread();
static CFRunLoopObserverRef observer;
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFOptionFlags activities = (kCFRunLoopBeforeWaiting | // before the run loop starts sleeping
kCFRunLoopExit); // before exiting a runloop run

observer = CFRunLoopObserverCreateWithHandler(NULL, // allocator
activities, // activities
YES, // repeats
INT_MAX, // order after CA transaction commits
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
ASDisplayNodeCAssertMainThread();
//在进入runloop休眠之前或者切换runloop模式导致runloop退出的时候会调用commit
[self commit];
});
CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes);
CFRelease(observer);
}

在commit中遍历添加到mainTransactionGroup中的TransactionContainer,调用asyncdisplaykit_currentAsyncTransaction的commit方法提交,
提交后会将asyncdisplaykit_currentAsyncTransaction设置为nil。

- (void)commit
{
ASDisplayNodeAssertMainThread();

if ([_containers count]) {
NSHashTable *containersToCommit = _containers;
_containers = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality];

//遍历添加到mainTransactionContainer中的TransactionContainer,调用asyncdisplaykit_currentAsyncTransaction的commit方法
for (id<ASAsyncTransactionContainer> container in containersToCommit) {
_ASAsyncTransaction *transaction = container.asyncdisplaykit_currentAsyncTransaction;
container.asyncdisplaykit_currentAsyncTransaction = nil;
[transaction commit];
}
}
}
  • ASAsyncTransactionContainer

ASAsyncTransactionContainer 是Transaction的容器,它其实是一个CALayer,最重要的有两个属性:

asyncdisplaykit_currentAsyncTransaction  当前的异步事务
asyncdisplaykit_asyncLayerTransactions 当前layer的所有异步事务

在 - (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously方法中会在整个层级树中找到最顶层的父节点,调用ASAsyncTransactionContainer的asyncdisplaykit_asyncTransaction方法为父节点创建asyncdisplaykit_asyncLayerTransactions以及asyncdisplaykit_currentAsyncTransaction,然后将当前TransactionContainer 添加到 mainTransactionGroup。代码如下:

- (_ASAsyncTransaction *)asyncdisplaykit_asyncTransaction
{
_ASAsyncTransaction *transaction = self.asyncdisplaykit_currentAsyncTransaction;
if (transaction == nil) {
//如果当前的异步Transaction为空
NSMutableSet<_ASAsyncTransaction *> *transactions = self.asyncdisplaykit_asyncLayerTransactions;
if (transactions == nil) {
//检查transactions是否为空,如果为空则创建一个并作为asyncdisplaykit_asyncLayerTransactions
transactions = ASCreatePointerBasedMutableSet();
self.asyncdisplaykit_asyncLayerTransactions = transactions;
}
__weak CALayer *weakSelf = self;
transaction = [[_ASAsyncTransaction alloc] initWithCompletionBlock:^(_ASAsyncTransaction *completedTransaction, BOOL cancelled) {
__strong CALayer *self = weakSelf;
if (self == nil) {
return;
}
[self.asyncdisplaykit_asyncLayerTransactions removeObject:completedTransaction];
if (self.asyncdisplaykit_asyncLayerTransactions.count == 0) {
// Reclaim object memory.
self.asyncdisplaykit_asyncLayerTransactions = nil;
}
[self asyncdisplaykit_asyncTransactionContainerDidCompleteTransaction:completedTransaction];
}];

//将transaction添加到transactions进行管理
[transactions addObject:transaction];
self.asyncdisplaykit_currentAsyncTransaction = transaction;
[self asyncdisplaykit_asyncTransactionContainerWillBeginTransaction:transaction];
}
//ASAsyncTransactionContainer----> transations ----> transaction
//将当前TransactionContainer 添加到 mainTransactionGroup,在runloop结束或者休眠之前会调用transaction的commit
[_ASAsyncTransactionGroup.mainTransactionGroup addTransactionContainer:self];
return transaction;
}
  • _ASAsyncTransaction

_ASAsyncTransaction 是啥?源码中给出了如下的描述:

@summary ASAsyncTransaction provides lightweight transaction semantics for asynchronous operations.
ASAsyncTransaction 为异步操作提供了轻量级的事务片段

@desc ASAsyncTransaction provides the following properties:

  • Transactions group an arbitrary number of operations, each consisting of an execution block and a completion block.
    Transactions 将任意多个operations打包在一起,每个Transaction包含一个executionBlok以及一个completionblock
  • The execution block returns a single object that will be passed to the completion block.
    execution block将会生成一个对象,然后将这个对象传递给completionblock
  • Execution blocks added to a transaction will run in parallel on the global background dispatch queues;
    the completion blocks are dispatched to the callback queue.
    添加到transaction的execution blocks将会在后台线程中并行运行,而completion block将会在回调队列中执行。
  • Every operation completion block is guaranteed to execute, regardless of cancelation.
    However, execution blocks may be skipped if the transaction is canceled.
    只要transaction不被取消每个completion block都会确保执行。
  • Operation completion blocks are always executed in the order they were added to the transaction, assuming the
    callback queue is serial of course.
    completion总是会按照加入到transaction的顺序执行。

应用到当前场景,简单得说就是,每个_ASAsyncTransaction包裹着display Block和 complage Block,display block将会在后台并行执行,然后将结果传递给complete block,complete block会按照顺序在主线程中顺序执行。

@implementation _ASAsyncTransaction
{
ASAsyncTransactionQueue::Group *_group;
NSMutableArray<ASAsyncTransactionOperation *> *_operations;
}

_ASAsyncTransaction 有两个主要的私有成员属性,_group和_operations。这就会扯到ASAsyncTransactionQueue,ASAsyncTransactionOperation,Operation,DispatchEntry

  • ASAsyncTransactionOperation

首先看下_operations 它其实是一个ASAsyncTransactionOperation数组,ASAsyncTransactionOperation类的结构如下,

@interface ASAsyncTransactionOperation : NSObject

- (instancetype)initWithOperationCompletionBlock:(asyncdisplaykit_async_transaction_operation_completion_block_t)operationCompletionBlock;

@property (nonatomic) asyncdisplaykit_async_transaction_operation_completion_block_t operationCompletionBlock;
@property id value; // set on bg queue by the operation block

@end

这里比较关键的是operationCompletionBlock和value,operationCompletionBlock是最前面提到的complete block 用于将display block 返回的UIImage传递给layer.content.

value 就是 display block 返回的UIImage,这里会在后台线程中生成UIImage,然后将其保存到value中,这个后面会介绍。最关键的是callAndReleaseCompletionBlock这个方法,CompletionBlock就是在这个方法中调用的,至于callAndReleaseCompletionBlock什么时候调用先留在后面。

- (void)callAndReleaseCompletionBlock:(BOOL)canceled;
{
ASDisplayNodeAssertMainThread();
if (_operationCompletionBlock) {
//调用CompletionBlock
_operationCompletionBlock(self.value, canceled);
_operationCompletionBlock = nil;
}
}

总而言之ASAsyncTransactionOperation封装着complete block,和一个存储display block执行结果的value值,并且在某个时期调用complete block

  • ASAsyncTransactionQueue

ASAsyncTransactionQueue 其实是一个并发数受限的轻量级operation队列,它包含了Group,Operation,DispatchEntry 三大对象。

Group 有点类似于 dispatch_group_t,它比较重要的有两个方法:

// 在指定队列中调度block
virtual void schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block) = 0;

// 所有先前调度的block都完成的时候发送的通知block
virtual void notify(dispatch_queue_t queue, dispatch_block_t block) = 0;

schedule 方法中会创建一个Operation然后通过DispatchEntry pushOperation添加到DispatchEntry,DispatchEntry中的线程数是受控制的,它最多是当前处理器内核数的两倍,如果还有线程数可以用,那么将会从DispatchEntry中按照优先级pop出一个operation执行它的block。要注意这里的block不是display block也不是complete block。而是执行display block后将值存储到operation.value中。

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

notify 方法是将block和queue封装成GroupNotify后添加到_notifyList,我们看到schedule方法中会调用_group->leave()

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

在leave中会将_notifyList取出来,将GroupNotify的block放到queue中执行。

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();
if (_releaseCalled) {
delete this;
}
}
}

那么这里就有两个问题,notify什么时候被调用,_notifyList放的是什么东西。这个也放在流程中讲,这里只需要了解,Group 实际上是一个线程数受控的类似于dispatch_group_t的任务队列,每次调用shedule的时候,会将block封装成一个Operation,然后添加到DispatchEntry,然后在DispatchEntry线程数没达到最大值的时候,会从DispatchEntry中取出来一个执行它的block,然后在leave中,将_notifyList运行完。

  • DispatchEntry && Operation

DispatchEntry 放着两个东西,一个是操作列表_operationQueue,一个是以优先级为key,操作列表为value的map _operationPriorityMap。在添加的时候会同时往这两个上面同时添加。

struct DispatchEntry // entry for each dispatch queue
{
typedef std::list<Operation> OperationQueue;
typedef std::list<OperationQueue::iterator> OperationIteratorList; // each item points to operation queue
typedef std::map<NSInteger, OperationIteratorList> OperationPriorityMap; // sorted by priority

OperationQueue _operationQueue;
OperationPriorityMap _operationPriorityMap;
int _threadCount;

Operation popNextOperation(bool respectPriority); // assumes locked mutex
void pushOperation(Operation operation); // assumes locked mutex
};

Operation 就是个结构体封装着block group和优先级

struct Operation
{
dispatch_block_t _block;
GroupImpl *_group;
NSInteger _priority;
};

稍稍总结下,我们会将一个block(非display block)包装在Operation 然后添加到DispatchEntry,中的OperationQueue和OperationPriorityMap,在Group的schedule方法中从DispatchEntry按照优先级取出后,在后台线程中执行block,并在执行完后执行leave方法。

这里有几个预留的问题下面要介绍的:

  1. Operation 中的block到底是啥
  2. Group的notify操作是干啥的,什么时候调用,leave 方法中的_notifyList放的是什么东西
1.4 异步绘制流程

有了上面的铺垫这里可以开始讲整个异步绘制流程了,我们先看下:- (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously

- (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously
{
ASDisplayNodeAssertMainThread();

//......
///1. CancelBlock displayBlock completetionBlock 初始化
//创建CancelBlock
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;
};
}

// 创建displayBlock,这里会调用delegate display 或者draw方法来获取到UIImage内容
asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:asynchronously
isCancelledBlock:isCancelledBlock
rasterizing:NO];
//.........

// 创建completionBlock:这个block会在当前异步事务完成后并完成渲染后在主线程被调用,或者在同步的情况下(asynchronously == NO)直接调用
asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id<NSObject> value, BOOL canceled){
ASDisplayNodeCAssertMainThread();
if (!canceled && !isCancelledBlock()) { //是否被取消,同步的情况下isCancelledBlock始终为NO
UIImage *image = (UIImage *)value; //value放置的是image
BOOL stretchable = (NO == UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero)); //图像是否是可拉伸的
if (stretchable) {
ASDisplayNodeSetResizableContents(layer, image);
} else {
//不可拉伸的情况下直接将内容作为contents
layer.contentsScale = self.contentsScale;
layer.contents = (id)image.CGImage;
}
//............
}
};

//............

if (asynchronously) {
// 异步渲染操作被包含在transaction中,transaction可以允许并行处理最终将结果以同步的方式应用到layer的contents属性中
// 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).

// 2. 我们先查看我们是否可以将自己添加到父类的transaction容器中
// First, look to see if we are expected to join a parent's transaction container.
CALayer *containerLayer = layer.asyncdisplaykit_parentTransactionContainer ? : layer;

// 如果transaction没有存在那么这里将会实例化并将它添加到_ASAsyncTransactionGroup,它会在runloop结束或者即将进入休眠之前提交出去
// 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;

//3. 将displayBlock添加到transaction,transaction将会立即启动执行
// 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 {
//同步的情况下直接通过displayBlock生成contents,并通过completionBlock设置到layer.content
UIImage *contents = (UIImage *)displayBlock();
completionBlock(contents, NO);
}
},

上面代码有三个关键点:

  1. 创建CancelBlock displayBlock completetionBlock 这里先不展开
  2. 顺着层级树找到最顶层的节点作为TransationContainer,并根据实际情况创建它的asyncdisplaykit_currentAsyncTransaction。并将TransationContainer添加到mainTransactionGroup。
  3. 调用addOperationWithBlock 往asyncdisplaykit_currentAsyncTransaction中添加display Block和complete Block.
- (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();
//...........
//创建一个TransationOperation将它添加到_operations,并且在displayQueue中执行displayBlock() 并将输出的UIImage保存到value中
ASAsyncTransactionOperation *operation = [[ASAsyncTransactionOperation alloc] initWithOperationCompletionBlock:completion];
[_operations addObject:operation];
_group->schedule(priority, queue, ^{
@autoreleasepool {
if (self.state != ASAsyncTransactionStateCanceled) {
operation.value = block();
}
}
});
}

addOperationWithBlock 完成两个工作:

  1. 将complete block 封装到ASAsyncTransactionOperation,并添加到_operations中
  2. 调用group 的 schedule,这里就可以解答第一个问题,schedule 中的block其实是如下的一个block,用于执行displayBlock后将输出的UIImage保存到value中
^{
@autoreleasepool {
if (self.state != ASAsyncTransactionStateCanceled) {
operation.value = block();
}
}
}

还有第二个问题:
Group的notify操作是干啥的,什么时候调用,leave 方法中的_notifyList放的是什么东西

ASAsyncTransaction的commit方法中

- (void)commit
{
ASDisplayNodeAssertMainThread();
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发出通知将后台得到的UImage通过completeBlock
_group->notify(dispatch_get_main_queue(), ^{
[self completeTransaction];
});
}
}

- (void)completeTransaction
{
ASDisplayNodeAssertMainThread();
ASAsyncTransactionState state = self.state;
if (state != ASAsyncTransactionStateComplete) {
BOOL isCanceled = (state == ASAsyncTransactionStateCanceled);
//遍历每个ASAsyncTransactionOperation 让后台生成的image通过ComplateBlock设置到layer.content上
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;

//通过回调通知当前transation结束
if (_completionBlock) {
_completionBlock(self, isCanceled);
}
}
}

我们知道commit是在Runloop进入休眠状态或者退出之前调用的,这里调用了group的notify方法,将completeTransaction 添加到_notifyList,在group shedule方法中执行block后将display block产生的UIImage 放到value中,然后调用leave方法,将_notifyList中的block放到queue中执行,也就是在主线程中执行complete block,将value传出来最终设置到layer.content中。

总结一下:

  1. 首先,在RunLoop进入休眠状态或者退出的时候通知到mainTransationGroup,mainTransationGroup 在commit方法中,遍历加入到mainTransationGroup中的每个TransationContainer,在TransationContainer中会对currentTransation调用一次commit方法。从而触发Group的notify方法,将在主线程执行的block操作放到Group的_notifyList中,这个bock操作就是将displayBlock生成的对象通过completeBlock传递给layer.content.

  2. 在displayAsyncLayer方法中将会创建displayBlock 和 complateBlock,然后封装到ASAsyncTransationOperation,并将它添加到当前页面的TransationContainer中的currentTransation中。在currentTransation中将diaplayBlock获取UIImage并存储到ASTransationOperation value中的操作封装成block,在Group中的DispathEntry中执行。执行完block后会调用Group leave,执行_notifyList中的任务,将ASTransationOperation value中的 UIImage通过complete Block设置到layer.content.

1.5 DisplayBlock && Complete Block
  1. Complete Block其实没多少东西可以介绍的,它的逻辑十分简单:就是将传入的value也就是displayBlock生成的UIImage设置到layer.content
asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id<NSObject> value, BOOL canceled){
ASDisplayNodeCAssertMainThread();
if (!canceled && !isCancelledBlock()) { //是否被取消,同步的情况下isCancelledBlock始终为NO
UIImage *image = (UIImage *)value; //value放置的是image
BOOL stretchable = (NO == UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero)); //图像是否是可拉伸的
if (stretchable) {
ASDisplayNodeSetResizableContents(layer, image);
} else {
//不可拉伸的情况下直接将内容作为contents
layer.contentsScale = self.contentsScale;
layer.contents = (id)image.CGImage;
}
//........
};
  1. DisplayBlock
- (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchronous:(BOOL)asynchronous
isCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock
rasterizing:(BOOL)rasterizing
{
ASDisplayNodeAssertMainThread();
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.
//如果没有display方法或者正在将子节点绘制到栅格化的父类上的时候需要创建Context
BOOL shouldCreateGraphicsContext = (flags.implementsImageDisplay == NO && rasterizing == NO);
//子节点时候需要栅格化
BOOL shouldBeginRasterizing = (rasterizing == NO && flags.rasterizesSubtree);
//是否实现了display方法
BOOL usesImageDisplay = flags.implementsImageDisplay;
//是否实现了drawRect方法
BOOL usesDrawRect = flags.implementsDrawRect;
//如果没有实现display/drawRect方法并且子节点不需要栅格化直接返回
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;
UIColor *backgroundColor = self.backgroundColor;
CGColorRef borderColor = self.borderColor;
CGFloat borderWidth = self.borderWidth;
CGFloat contentsScaleForDisplay = _contentsScaleForDisplay;

__instanceLock__.unlock();

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

// Only the -display methods should be called if we can't size the graphics buffer to use.
if (CGRectIsEmpty(bounds) && (shouldBeginRasterizing || shouldCreateGraphicsContext)) {
return nil;
}
//..........
//是否栅格化
if (shouldBeginRasterizing) {
// Collect displayBlocks for all descendants.
// 用于放置子节点的displayBlock在父节点的displayBlock中直接遍历各个子节点的displayBlock
NSMutableArray *displayBlocks = [[NSMutableArray alloc] init];
[self _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks];
CHECK_CANCELLED_AND_RETURN_NIL();

opaque = opaque && CGColorGetAlpha(backgroundColor.CGColor) == 1.0f;

//这里会将整个container的所有子节点的displayBlock打包给displayBlock,在执行container的displayBlock后会调用这些子节点的displayBlock将其绘制到container上
displayBlock = ^id{
CHECK_CANCELLED_AND_RETURN_NIL();
UIImage *image = ASGraphicsCreateImageWithOptions(bounds.size, opaque, contentsScaleForDisplay, nil, isCancelledBlock, ^{
for (dispatch_block_t block in displayBlocks) {
if (isCancelledBlock()) return;
block();
}
});

ASDN_DELAY_FOR_DISPLAY();
return image;
};
} else {

displayBlock = ^id{

CHECK_CANCELLED_AND_RETURN_NIL();

__block UIImage *image = nil;
void (^workWithContext)() = ^{
CGContextRef currentContext = UIGraphicsGetCurrentContext();

if (shouldCreateGraphicsContext && !currentContext) {
ASDisplayNodeAssert(NO, @"Failed to create a CGContext (size: %@)", NSStringFromCGSize(bounds.size));
return;
}

// 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.
[self __willDisplayNodeContentWithRenderingContext:currentContext drawParameters:drawParameters];
//根据情况调用display方法或者drawRect方法
if (usesImageDisplay) { // If we are using a display method, we'll get an image back directly.
image = [self.class displayWithParameters:drawParameters isCancelled:isCancelledBlock];
} else if (usesDrawRect) { // If we're using a draw method, this will operate on the currentContext.
[self.class drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
}
[self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor];
ASDN_DELAY_FOR_DISPLAY();
};

if (shouldCreateGraphicsContext) {
return ASGraphicsCreateImageWithOptions(bounds.size, opaque, contentsScaleForDisplay, nil, isCancelledBlock, workWithContext);
} else {
workWithContext();
return image;
}
};
}
//...........
return displayBlock;
}

display Block 的开始会先通过flags来判断当前的子节点是否需要栅格化,flags这个结构体变量主要用来标记该Node的一些状态属性,以及是否重载了某些方法。如果需要栅格化,那么就需要遍历各个子节点,从各个子节点中获取自身的display Block放置到一个名字为displayBlocks的数组中。这些Block负责生成自身的UIImage.根节点的displayBlock中包含着子节点的display block.在绘制页面的时候,通过这些子节点的display block在绘图的context中将自己绘制出来。这里比较关键的是_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock方法:

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

__instanceLock__.lock();
BOOL rasterizingFromAscendent = (_hierarchyState & ASHierarchyStateRasterized);
__instanceLock__.unlock();

// 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.rasterizesSubtree) {
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];
}

// If we pushed a transform, pop it by adding a display block that does nothing other than that.
if (shouldDisplay) {
// Since this block is pure, we can store it statically.
static dispatch_block_t popBlock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
popBlock = ^{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextRestoreGState(context);
};
});
[displayBlocks addObject:popBlock];
}
}

_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock 这个方法中也调用_displayBlockWithAsynchronous来获取displayBlock,只不过需要注意的是rasterizing参数为YES导致_displayBlockWithAsynchronous中shouldBeginRasterizing为NO.直接走displayWithParameters/drawRect部分。绘制子节点的工作在pushAndDisplayBlock中完成,pushAndDisplayBlock就是被添加到displayBlocks的子block。

如果不需要栅格化,就直接走displayWithParameters/drawRect部分具体是displayWithParameters还是drawRect取决于flags中的标志,如果走displayWithParameters将会直接获取image返回,如果是drawRect将会在当前Context中绘制自身绘制。

- (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchronous:(BOOL)asynchronous
isCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock
rasterizing:(BOOL)rasterizing
{
ASDisplayNodeAssertMainThread();
//........
//是否实现了display方法
BOOL usesImageDisplay = flags.implementsImageDisplay;
//是否实现了drawRect方法
BOOL usesDrawRect = flags.implementsDrawRect;
//..........
//是否栅格化
if (shouldBeginRasterizing) {
//.......
} else {
displayBlock = ^id{
__block UIImage *image = nil;
void (^workWithContext)() = ^{
CGContextRef currentContext = UIGraphicsGetCurrentContext();
// .......
// 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.
[self __willDisplayNodeContentWithRenderingContext:currentContext drawParameters:drawParameters];
//根据情况调用display方法或者drawRect方法
if (usesImageDisplay) { // If we are using a display method, we'll get an image back directly.
image = [self.class displayWithParameters:drawParameters isCancelled:isCancelledBlock];
} else if (usesDrawRect) { // If we're using a draw method, this will operate on the currentContext.
[self.class drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
}
[self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor];
ASDN_DELAY_FOR_DISPLAY();
};

if (shouldCreateGraphicsContext) {
return ASGraphicsCreateImageWithOptions(bounds.size, opaque, contentsScaleForDisplay, nil, isCancelledBlock, workWithContext);
} else {
workWithContext();
return image;
}
};
}
//...........
return displayBlock;
}
1. Texture 布局源码分析

最早iOS平台上的布局方式是基于frame的布局,但是后续iPhone推出了一系列不同尺寸的设备,这时候frame布局方式就显得十分麻烦,为了解决这个问题Auto Layout 就诞生了,如果使用过Auto Layout原生接口进行布局的经历,我相信一定是十分难受的,但是随着Masonry的推出,让Auto Layout逐渐被开发者所接受,
但是Auto Layout与frame 布局不同的是我们给出的是组件与组件之间的约束关系,然后由系统布局引擎帮我们计算出每个组件的实际frame。也就是说Auto Layout最终还是要转换成组件的frame,只不过这个转换系统帮我们做了,我们只需要给出约束条件就可以了。但是Auto Layout每个组件的约束最终都会转换为一个N元一次的线性等式或者不等式,要计算出整个页面每个组件frame的值,就需要根据这些N元一次的线性等式或者不等式组成的不等式组解出结果。这个时间是不确定的,并且界面越复杂不等式组也就越复杂,计算所耗费的时间也就越多。最要命的是它还会强制视图在主线程上布局。如果这个时间超过16.67ms就会导致界面的卡顿。

那么我们有没有一套可以不用手动计算布局参数,又能有较高的布局效率的布局引擎呢?我们先看下Texture的布局引擎的源码,在这里寻找下我们需要的答案。

Texture 布局引擎是可以在后台线程中运行的布局引擎,它采用了目前前端比较流行的FlexBox布局形式,具体的使用大家可以看下之前的介绍Texture布局使用的博客。Texture 2.X 目前采用了两套布局引擎:LayoutSpec 布局规范 和 Yoga 布局引擎,这里先介绍 LayoutSpec 布局规范引擎。

Texture 布局引擎比较突出的特点是能够在后台计算布局,并且能够缓存布局结果。

Texture中可以通过如下四种方式指定布局:

* 提供 layoutSpecBlock
* 覆写 - layoutSpecThatFits: 方法
* 覆写 - calculateSizeThatFits: 方法
* 覆写 - calculateLayoutThatFits: 方法
    • layoutSpecThatFits: 与 layoutSpecBlock 是完全等价的只不过是实现的形式不一样而已。
    • calculateSizeThatFits: 这种方式提供了手动布局的方式,通过在该方法内对 frame 进行计算,返回一个当前视图的 CGSize,它和UIView 中的 -[UIView sizeThatFits:] 非常相似 只不过 Texture中的布局会对所有计算出的布局进行缓存来提高性能。
    • calculateLayoutThatFits: 把上面的两种布局方式:手动布局和 Spec 布局封装成了一个接口,这样,无论是 CGSize 还是 ASLayoutSpec 最后都会以 ASLayout 的形式返回给方法调用者。一般推荐calculateLayoutThatFits覆写这个方法,而不是layoutSpecThatFits。

还是老规矩在介绍流程之前先熟悉下关键对象的组成:

  1. ASLayoutElement
@protocol ASLayoutElement <ASLayoutElementExtensibility, ASTraitEnvironment, ASLayoutElementAsciiArtProtocol>

//包括ASLayoutElementTypeLayoutSpec以及ASLayoutElementTypeDisplayNode,前者为布局约束元素,后一个表明为显示节点元素

@property (nonatomic, readonly) ASLayoutElementType layoutElementType;
/**
* 尺寸约束
*/
@property (nonatomic, readonly) ASLayoutElementStyle *style;
/**
* 当前节点的子节点集合
*/
- (nullable NSArray<id<ASLayoutElement>> *)sublayoutElements;

/**
* 要求节点基于给定的尺寸范围返回一个ASLayout布局
* 这个方法会缓存约束条件和布局结果,所以子类不能覆盖它,因为它会缓存calculateLayoutThatFits获得的结果,如果没有缓存结果那么这个方法的将会十分耗时
*/
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize;

/**
* 让子元素在calculateLayoutThatFits计算它们的布局,这个是供给内部使用的我们不应该覆写这个方法,但是可以通过覆写-calculateLayoutThatFits来代替,constrainedSize 是一个最大值和最小值的一个限制,最终得到的尺寸必须在这个范围内。这个方法也是会缓存计算的结果和约束条件
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize;

/**
* 通过覆写这个方法来计算当前布局元素的布局
*/
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize;

@end
  1. ASLayout

@interface ASLayout : NSObject

/**
* 当前布局对象
*/
@property (nonatomic, weak, readonly) id<ASLayoutElement> layoutElement;

/**
* 节点类型
*/
@property (nonatomic, readonly) ASLayoutElementType type;

/**
* 当前布局的Size
*/
@property (nonatomic, readonly) CGSize size;

/**
* 在父节点的位置
*/
@property (nonatomic, readonly) CGPoint position;

/**
* 子节点的布局
*/
@property (nonatomic, copy, readonly) NSArray<ASLayout *> *sublayouts;

/**
* 某个节点的位置
*/
- (CGRect)frameForElement:(id<ASLayoutElement>)layoutElement;

/**
* 根据postion和size计算出来的frame
*/
@property (nonatomic, readonly) CGRect frame;

//......

/**
* Traverses the existing layout tree and generates a new tree that represents only ASDisplayNode layouts
*/
- (ASLayout *)filteredNodeLayoutTree NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT;

@end

在介绍关键都类之后我们来看下整个布局流程:
我们以手动调用节点都layoutThatFits方法作为入口点。在layoutThatFits:中简单得转调了下layoutThatFits:parentSize

- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize
{
return [self layoutThatFits:constrainedSize parentSize:constrainedSize.max];
}

- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize
{
//......
ASLayout *layout = nil;
//在每次调用-setNeedsLayout / -invalidateCalculatedLayout 方法的时候_layoutVersion会+1
NSUInteger version = _layoutVersion;
//先检查_calculatedDisplayNodeLayout是否可用,如果可用则优先使用
if (_calculatedDisplayNodeLayout.isValid(constrainedSize, parentSize, version)) {
layout = _calculatedDisplayNodeLayout.layout;
} else if (_pendingDisplayNodeLayout.isValid(constrainedSize, parentSize, version)) {
//检查_pendingDisplayNodeLayout是否可用,如果可用则优先使用
layout = _pendingDisplayNodeLayout.layout;
} else {
//如果_calculatedDisplayNodeLayout和_pendingDisplayNodeLayout都不可用则新建一个ASLayout作为_pendingDisplayNodeLayout
// Create a pending display node layout for the layout pass
layout = [self calculateLayoutThatFits:constrainedSize
restrictedToSize:self.style.size
relativeToParentSize:parentSize];
_pendingDisplayNodeLayout = ASDisplayNodeLayout(layout, constrainedSize, parentSize,version);
}

return layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}];
}

最初的时候_calculatedDisplayNodeLayout和_pendingDisplayNodeLayout都是不可用的,所以layout是由calculateLayoutThatFits:restrictedToSize:relativeToParentSize 返回的,然后将其包裹成ASDisplayNodeLayout赋值给_pendingDisplayNodeLayout,这里需要注意的是这里的_layoutVersion,每次调用-setNeedsLayout / -invalidateCalculatedLayout 方法的时候_layoutVersion会+1,当版本不对的时候isValid就会返回NO,这里的_calculatedDisplayNodeLayout和_pendingDisplayNodeLayout应该是一个布局缓存。这个等后续的时候进一步查看。

- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
restrictedToSize:(ASLayoutElementSize)size
relativeToParentSize:(CGSize)parentSize
{
ASSizeRange styleAndParentSize = ASLayoutElementSizeResolve(self.style.size, parentSize);
const ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, styleAndParentSize);
ASLayout *result = [self calculateLayoutThatFits:resolvedRange];
return result;
}

在calculateLayoutThatFits中会对限制的范围做个重新修正,再以修正后的约束范围作为参数计算布局。

- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
{
//调用方法之前的检查,必须提供layoutSpecBlock或者重写calculateLayoutThatFits,calculateSizeThatFits或者layoutSpecThatFits
__ASDisplayNodeCheckForLayoutMethodOverrides;

//根据不同布局引擎类型实现不同的布局
switch (self.layoutEngineType) {
//使用布局规范引擎
case ASLayoutEngineTypeLayoutSpec:
return [self calculateLayoutLayoutSpec:constrainedSize];
#if YOGA
//使用YOGA布局引擎
case ASLayoutEngineTypeYoga:
return [self calculateLayoutYoga:constrainedSize];
#endif
default:
break;
}
return nil;
}

在Texture 2.0开始引入了YOGA布局引擎,YOGA布局是FaceBook推出的布局形式,我们这里以Texture 默认采用的LayoutSpec作为研究对象进行介绍。

- (ASLayout *)calculateLayoutLayoutSpec:(ASSizeRange)constrainedSize
{
// 没有通过_layoutSpecBlock也没有实现LayoutSpecThatFits指定布局则直接通过calculateSizeThatFits手动指定size
if (_layoutSpecBlock == NULL && (_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) == 0) {
//手动指定尺寸
CGSize size = [self calculateSizeThatFits:constrainedSize.max];
return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:nil];
}

// 通过_layoutSpecBlock 或者 layoutSpecThatFits来从node中获得ASLayoutElement
id<ASLayoutElement> layoutElement = [self _locked_layoutElementThatFits:constrainedSize];

// 调用ASLayoutElement的layoutThatFits:方法计算得到ASLayout。
ASLayout *layout = ({
AS::SumScopeTimer t(_layoutComputationTotalTime, measureLayoutComputation);
[layoutElement layoutThatFits:constrainedSize];
});

//.......
layout = [layout filteredNodeLayoutTree];

return layout;
}
  1. 首先先判断是否有设置过_layoutSpecBlock或者实现过layoutSpecThatFits:这两者其实是等效的,如果这两者都没有,那么就只能通过calculateSizeThatFits手动指定size。将用户手动指定的Size封装成ASLayout返回

  2. 如果有实现layoutSpecThatFits:或者有设置过_layoutSpecBlock那么就通过各自的方法返回实现了ASLayoutElement协议的对象。

  3. 通过调用layoutThatFits返回ASLayout,这里会调用各个ASLayoutSpec子类的calculateLayoutThatFits方法。这里以最简单的ASWrapperLayoutSpec为例子进行分析:

- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
{
NSArray *children = self.children;
const auto count = children.count;
ASLayout *rawSublayouts[count];
int i = 0;
CGSize size = constrainedSize.min;
//遍历子节点,获得子节点的ASLayout
for (id<ASLayoutElement> child in children) {
ASLayout *sublayout = [child layoutThatFits:constrainedSize parentSize:constrainedSize.max];
sublayout.position = CGPointZero;
//获得最大的宽高
size.width = MAX(size.width, sublayout.size.width);
size.height = MAX(size.height, sublayout.size.height);
//将子节点的布局保存起来
rawSublayouts[i++] = sublayout;
}
const auto sublayouts = [NSArray<ASLayout *> arrayByTransferring:rawSublayouts count:i];
//将size转换为ASLayout
return [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts];
}

ASWrapperLayoutSpec的功能就是用一个布局将轮廓包起开,这里会遍历每个子节点获取子节点的尺寸,获取最大的size,并通过layoutWithLayoutElement将size转换为ASLayout返回。

calculateLayoutLayoutSpec针对上面获得的ASLayout还有最后一步处理:

layout = [layout filteredNodeLayoutTree];

什么是扁平化,我们之前的步骤都是针对节点的宽高。扁平化就是针对position进行修正。

- (ASLayout *)filteredNodeLayoutTree NS_RETURNS_RETAINED
{

struct Context {
unowned ASLayout *layout;
CGPoint absolutePosition;
};
// Queue used to keep track of sublayouts while traversing this layout in a DFS fashion.
std::deque<Context> queue;
//将_sublayouts数据先填到queue
for (ASLayout *sublayout in _sublayouts) {
queue.push_back({sublayout, sublayout.position});
}
//这个是扁平化的子布局
std::vector<ASLayout *> flattenedSublayouts;
while (!queue.empty()) {
//从queue中拿出数据
const Context context = std::move(queue.front());
queue.pop_front();

unowned ASLayout *layout = context.layout;
// Direct ivar access to avoid retain/release, use existing +1.
const NSUInteger sublayoutsCount = layout->_sublayouts.count;
const CGPoint absolutePosition = context.absolutePosition;
//是否是DisplayNode节点,如果是节点就不需要扁平化,看absolutePosition是否和layout.position是否相同,如果不相同就需要新建一个放到flattenedSublayouts
if (ASLayoutIsDisplayNodeType(layout)) {
if (sublayoutsCount > 0 || CGPointEqualToPoint(ASCeilPointValues(absolutePosition), layout.position) == NO) {
// Only create a new layout if the existing one can't be reused, which means it has either some sublayouts or an invalid absolute position.
const auto newLayout = [ASLayout layoutWithLayoutElement:layout->_layoutElement
size:layout.size
position:absolutePosition
sublayouts:@[]];
flattenedSublayouts.push_back(newLayout);
} else {
flattenedSublayouts.push_back(layout);
}
} else if (sublayoutsCount > 0) {
//如果是一个布局容器,那么就需要根据节点之间的关系修正positon位置
// Fast-reverse-enumerate the sublayouts array by copying it into a C-array and push_front'ing each into the queue.
unowned ASLayout *rawSublayouts[sublayoutsCount];
[layout->_sublayouts getObjects:rawSublayouts range:NSMakeRange(0, sublayoutsCount)];
for (NSInteger i = sublayoutsCount - 1; i >= 0; i--) {
queue.push_front({rawSublayouts[i], absolutePosition + rawSublayouts[i].position});
}
}
}
//将flattenedSublayouts转换成ASLayout
NSArray *array = [NSArray arrayByTransferring:flattenedSublayouts.data() count:flattenedSublayouts.size()];
ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:array];
[layout retainSublayoutElements];
return layout;
}

那我们怎么将ASLayout应用到节点的frame完成布局呢?我们在上面介绍ASLayout的时候有提到一个方法:

- (CGRect)frameForElement:(id<ASLayoutElement>)layoutElement;

它传入一个节点或者节点容器,会返回一个frame:

- (CGRect)frameForElement:(id<ASLayoutElement>)layoutElement
{
for (ASLayout *l in _sublayouts) {
if (l->_layoutElement == layoutElement) {
return l.frame;
}
}
return CGRectNull;
}

frameForElement这个方法在ASDisplayNode+Layout.mm类中的_layoutSublayouts调用,这里遍历当前节点的子节点,将每个节点传入frameForElement,获得frame,再将frame赋给node.frame,完成布局。

- (void)_layoutSublayouts
{
ASDisplayNodeAssertThreadAffinity(self);
//............
for (ASDisplayNode *node in self.subnodes) {
CGRect frame = [layout frameForElement:node];
if (CGRectIsNull(frame)) {
// There is no frame for this node in our layout.
// This currently can happen if we get a CA layout pass
// while waiting for the client to run animateLayoutTransition:
} else {
node.frame = frame;
}
}
}

可能大家还会有一个疑问,我们上面介绍的都是posistion,size那frame怎么得到的?其实frame 就是一个postion和size组合而来。

- (CGRect)frame
{
CGRect subnodeFrame = CGRectZero;
CGPoint adjustedOrigin = _position;
//.......
subnodeFrame.origin = adjustedOrigin;
CGSize adjustedSize = _size;
//......
subnodeFrame.size = adjustedSize;
return subnodeFrame;
}

那么是怎么触发_layoutSublayouts呢?我们看下ASDisplayNode.mm的__layout方法。

- (void)__layout
{
//......
BOOL loaded = NO;
{
//........
// This method will confirm that the layout is up to date (and update if needed).
// Importantly, it will also APPLY the layout to all of our subnodes if (unless parent is transitioning).
l.unlock();
// 如果过期了就会重新计算,否则使用缓存的布局
[self _u_measureNodeWithBoundsIfNecessary:bounds];
l.lock();
//布局占位图
[self _locked_layoutPlaceholderIfNecessary];
}
//调用_layoutSublayouts
[self _layoutSublayouts];

if (loaded) {
ASPerformBlockOnMainThread(^{
[self layout];
[self _layoutClipCornersIfNeeded];
[self _layoutDidFinish];
});
}
[self _fallbackUpdateSafeAreaOnChildren];
}

而__layout 是再layoutIfNeeded中调用的。

- (void)layoutIfNeeded
{
//.......
if (shouldApply) {
// The node is loaded and we're on main.
// Message the view or layer which in turn will call __layout on us (see -[_ASDisplayLayer layoutSublayers]).
[viewOrLayer layoutIfNeeded];
} else if (loaded == NO) {
// The node is not loaded and we're not on main.
[self __layout];
}
}

提到layoutIfNeeded我们一定会联想到setNeedsLayout,再UIView中会将UIView标记为dirty需要布局。那么ASDisplayNode呢?

- (void)setNeedsLayout
{
//.....
if (shouldApply) {
// The node is loaded and we're on main.
// Quite the opposite of setNeedsDisplay, we must call __setNeedsLayout before messaging
// the view or layer to ensure that measurement and implicitly added subnodes have been handled.
[self __setNeedsLayout];
[viewOrLayer setNeedsLayout];
} else if (loaded == NO) {
// The node is not loaded and we're not on main.
[self __setNeedsLayout];
}
}
- (void)__setNeedsLayout
{
[self invalidateCalculatedLayout];
}
- (void)invalidateCalculatedLayout
{
_layoutVersion++;
}

看到了吧它只是将_layoutVersion加1,这样再使用的时候由于版本不对就将原来的布局过期处理。

整个布局流程如下图所示:

Contents
  1. 1. 1. Texture 异步绘制源码分析
    1. 1.1. 1.1 整体流程图
    2. 1.2. 1.2 触发异步绘制的入口点
    3. 1.3. 1.3 几个重要类之间的关系
    4. 1.4. 1.4 异步绘制流程
    5. 1.5. 1.5 DisplayBlock && Complete Block
  2. 2. 1. Texture 布局源码分析