//
|
// 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
|