- (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)registerAsMainRunloopObserver { ASDisplayNodeAssertMainThread(); staticCFRunLoopObserverRef observer; CFRunLoopRef runLoop = CFRunLoopGetCurrent(); CFOptionFlags activities = (kCFRunLoopBeforeWaiting | // before the run loop starts sleeping kCFRunLoopExit); // before exiting a runloop run
@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的顺序执行。
@property (nonatomic) asyncdisplaykit_async_transaction_operation_completion_block_t operationCompletionBlock; @property id value; // set on bg queue by the operation block
value 就是 display block 返回的UIImage,这里会在后台线程中生成UIImage,然后将其保存到value中,这个后面会介绍。最关键的是callAndReleaseCompletionBlock这个方法,CompletionBlock就是在这个方法中调用的,至于callAndReleaseCompletionBlock什么时候调用先留在后面。
// 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); } }); } }
//............ 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); } },
- (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 setstateto Complete, even if we were cancelled, toblockany 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); } } }
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(); returnnil; } 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)) { returnnil; } //.......... //是否栅格化 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();
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]; } elseif (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(); };
- (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);
// 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 inself.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; staticdispatch_once_t onceToken; dispatch_once(&onceToken, ^{ popBlock = ^{ CGContextRef context = UIGraphicsGetCurrentContext(); CGContextRestoreGState(context); }; }); [displayBlocks addObject:popBlock]; } }
- (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]; } elseif (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(); };
/** * Traverses the existing layout tree and generates a new tree that represents only ASDisplayNode layouts */ - (ASLayout *)filteredNodeLayoutTree NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT;
- (ASLayout *)filteredNodeLayoutTree NS_RETURNS_RETAINED { structContext { 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. constauto newLayout = [ASLayout layoutWithLayoutElement:layout->_layoutElement size:layout.size position:absolutePosition sublayouts:@[]]; flattenedSublayouts.push_back(newLayout); } else { flattenedSublayouts.push_back(layout); } } elseif (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; }
- (void)_layoutSublayouts { ASDisplayNodeAssertThreadAffinity(self); //............ for (ASDisplayNode *node inself.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; } } }
- (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]; } }
- (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++; }