New file |
| | |
| | | // |
| | | // YYAsyncLayer.m |
| | | // YYKit <https://github.com/ibireme/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 <libkern/OSAtomic.h> |
| | | #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<YYAsyncLayerDelegate> delegate = 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; |
| | | 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); |
| | | return; |
| | | } |
| | | |
| | | dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{ |
| | | if (isCancelled()) return; |
| | | UIGraphicsBeginImageContextWithOptions(size, opaque, scale); |
| | | CGContextRef context = UIGraphicsGetCurrentContext(); |
| | | 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(); |
| | | 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 |