// // YYAsyncLayer.m // YYKit // // Created by ibireme on 15/4/11. // Copyright (c) 2015 ibireme. // // This source code is licensed under the MIT-style license found in the // LICENSE file in the root directory of this source tree. // #import "YYAsyncLayer.h" #import "YYSentinel.h" #if __has_include("YYDispatchQueuePool.h") #import "YYDispatchQueuePool.h" #else #import #endif /// Global display queue, used for content rendering. static dispatch_queue_t YYAsyncLayerGetDisplayQueue() { #ifdef YYDispatchQueuePool_h return YYDispatchQueueGetForQOS(NSQualityOfServiceUserInitiated); #else #define MAX_QUEUE_COUNT 16 static int queueCount; static dispatch_queue_t queues[MAX_QUEUE_COUNT]; static dispatch_once_t onceToken; static int32_t counter = 0; dispatch_once(&onceToken, ^{ queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount; queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount; if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) { for (NSUInteger i = 0; i < queueCount; i++) { dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0); queues[i] = dispatch_queue_create("com.ibireme.yykit.render", attr); } } else { for (NSUInteger i = 0; i < queueCount; i++) { queues[i] = dispatch_queue_create("com.ibireme.yykit.render", DISPATCH_QUEUE_SERIAL); dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); } } }); int32_t cur = OSAtomicIncrement32(&counter); if (cur < 0) cur = -cur; return queues[(cur) % queueCount]; #undef MAX_QUEUE_COUNT #endif } static dispatch_queue_t YYAsyncLayerGetReleaseQueue() { #ifdef YYDispatchQueuePool_h return YYDispatchQueueGetForQOS(NSQualityOfServiceDefault); #else return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); #endif } @implementation YYAsyncLayerDisplayTask @end @implementation YYAsyncLayer { YYSentinel *_sentinel; } #pragma mark - Override + (id)defaultValueForKey:(NSString *)key { if ([key isEqualToString:@"displaysAsynchronously"]) { return @(YES); } else { return [super defaultValueForKey:key]; } } - (instancetype)init { self = [super init]; static CGFloat scale; //global static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ scale = [UIScreen mainScreen].scale; }); self.contentsScale = scale; _sentinel = [YYSentinel new]; _displaysAsynchronously = YES; return self; } - (void)dealloc { [_sentinel increase]; } - (void)setNeedsDisplay { [self _cancelAsyncDisplay]; [super setNeedsDisplay]; } - (void)display { super.contents = super.contents; [self _displayAsync:_displaysAsynchronously]; } #pragma mark - Private - (void)_displayAsync:(BOOL)async { __strong id delegate = (id)self.delegate; YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask]; 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; 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 && context) { 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()) { 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 { [_sentinel increase]; if (task.willDisplay) task.willDisplay(self); UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, self.contentsScale); CGContextRef context = UIGraphicsGetCurrentContext(); if (self.opaque && context) { 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]; } @end