Last active
August 24, 2016 09:31
-
-
Save YauzZ/d2970da7f728c55599d3832458d2f24f to your computer and use it in GitHub Desktop.
YYLayer中,异步渲染的核心逻辑代码
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- (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, just use nil for the displaySentinel and go | |
// 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. | |
ASSentinel *displaySentinel = (asynchronously ? _displaySentinel : nil); | |
int32_t displaySentinelValue = [displaySentinel increment]; | |
asdisplaynode_iscancelled_block_t isCancelledBlock = ^{ | |
return BOOL(displaySentinelValue != displaySentinel.value); | |
}; | |
// 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]; | |
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 系统回调,用于刷新Content | |
- (void)display { | |
super.contents = super.contents; | |
[self _displayAsync:_displaysAsynchronously]; | |
} | |
#pragma mark - Private | |
- (void)_displayAsync:(BOOL)async { | |
// 强引用保证不被释放 | |
__strong id<YYAsyncLayerDelegate> delegate = self.delegate; | |
// 获取异步渲染任务 | |
YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask]; | |
// 无显示block则直接清空内容 | |
if (!task.display) { | |
if (task.willDisplay) task.willDisplay(self); | |
self.contents = nil; | |
if (task.didDisplay) task.didDisplay(self, YES); | |
return; | |
} | |
// 区分异步渲染跟同步渲染 | |
if (async) { | |
if (task.willDisplay) task.willDisplay(self); | |
// 线程安全计数器 | |
YYSentinel *sentinel = _sentinel; | |
int32_t value = sentinel.value; | |
// 取消渲染Block,判断计数器是否有增加,很巧妙的实现 | |
BOOL (^isCancelled)() = ^BOOL() { | |
return value != sentinel.value; | |
}; | |
CGSize size = self.bounds.size; | |
BOOL opaque = self.opaque; | |
CGFloat scale = self.contentsScale; | |
CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL; | |
if (size.width < 1 || size.height < 1) { | |
CGImageRef image = (__bridge_retained CGImageRef)(self.contents); | |
self.contents = nil; | |
if (image) { | |
// 异步线程释放,减轻主线程压力 | |
dispatch_async(YYAsyncLayerGetReleaseQueue(), ^{ | |
CFRelease(image); | |
}); | |
} | |
if (task.didDisplay) task.didDisplay(self, YES); | |
CGColorRelease(backgroundColor); | |
return; | |
} | |
dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{ | |
if (isCancelled()) { | |
CGColorRelease(backgroundColor); | |
return; | |
} | |
UIGraphicsBeginImageContextWithOptions(size, opaque, scale); | |
CGContextRef context = UIGraphicsGetCurrentContext(); | |
if (opaque) { | |
CGContextSaveGState(context); { | |
if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) { | |
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); | |
CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale)); | |
CGContextFillPath(context); | |
} | |
if (backgroundColor) { | |
CGContextSetFillColorWithColor(context, backgroundColor); | |
CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale)); | |
CGContextFillPath(context); | |
} | |
} CGContextRestoreGState(context); | |
CGColorRelease(backgroundColor); | |
} | |
task.display(context, size, isCancelled); | |
if (isCancelled()) { | |
// 每走一步,都对Cancel判断一次,尽可能减少渲染次数 | |
UIGraphicsEndImageContext(); | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
if (task.didDisplay) task.didDisplay(self, NO); | |
}); | |
return; | |
} | |
UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); | |
UIGraphicsEndImageContext(); | |
if (isCancelled()) { | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
if (task.didDisplay) task.didDisplay(self, NO); | |
}); | |
return; | |
} | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
if (isCancelled()) { | |
if (task.didDisplay) task.didDisplay(self, NO); | |
} else { | |
self.contents = (__bridge id)(image.CGImage); | |
if (task.didDisplay) task.didDisplay(self, YES); | |
} | |
}); | |
}); | |
} else { | |
// 同步渲染前先增加计数,标明这是一次新渲染,旧的未完成的渲染都Cancel掉 | |
[_sentinel increase]; | |
if (task.willDisplay) task.willDisplay(self); | |
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, self.contentsScale); | |
CGContextRef context = UIGraphicsGetCurrentContext(); | |
if (self.opaque) { | |
CGSize size = self.bounds.size; | |
size.width *= self.contentsScale; | |
size.height *= self.contentsScale; | |
CGContextSaveGState(context); { | |
if (!self.backgroundColor || CGColorGetAlpha(self.backgroundColor) < 1) { | |
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); | |
CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height)); | |
CGContextFillPath(context); | |
} | |
if (self.backgroundColor) { | |
CGContextSetFillColorWithColor(context, self.backgroundColor); | |
CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height)); | |
CGContextFillPath(context); | |
} | |
} CGContextRestoreGState(context); | |
} | |
task.display(context, self.bounds.size, ^{return NO;}); | |
UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); | |
UIGraphicsEndImageContext(); | |
self.contents = (__bridge id)(image.CGImage); | |
if (task.didDisplay) task.didDisplay(self, YES); | |
} | |
} | |
- (void)_cancelAsyncDisplay { | |
// 增加计数,标明取消之前的渲染 | |
[_sentinel increase]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
YYKit 中用计数器来中断刷新过程的思路,应该是来自ASDK。