235 lines
8.1 KiB
Objective-C
235 lines
8.1 KiB
Objective-C
//
|
|
// YYTextAsyncLayer.m
|
|
// YYText <https://github.com/ibireme/YYText>
|
|
//
|
|
// 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 "YYTextAsyncLayer.h"
|
|
#import <libkern/OSAtomic.h>
|
|
|
|
|
|
/// Global display queue, used for content rendering.
|
|
static dispatch_queue_t YYTextAsyncLayerGetDisplayQueue() {
|
|
#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.text.render", attr);
|
|
}
|
|
} else {
|
|
for (NSUInteger i = 0; i < queueCount; i++) {
|
|
queues[i] = dispatch_queue_create("com.ibireme.text.render", DISPATCH_QUEUE_SERIAL);
|
|
dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
|
|
}
|
|
}
|
|
});
|
|
uint32_t cur = (uint32_t)OSAtomicIncrement32(&counter);
|
|
return queues[(cur) % queueCount];
|
|
#undef MAX_QUEUE_COUNT
|
|
}
|
|
|
|
static dispatch_queue_t YYTextAsyncLayerGetReleaseQueue() {
|
|
#ifdef YYDispatchQueuePool_h
|
|
return YYDispatchQueueGetForQOS(NSQualityOfServiceDefault);
|
|
#else
|
|
return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
|
|
#endif
|
|
}
|
|
|
|
|
|
/// a thread safe incrementing counter.
|
|
@interface _YYTextSentinel : NSObject
|
|
/// Returns the current value of the counter.
|
|
@property (atomic, readonly) int32_t value;
|
|
/// Increase the value atomically. @return The new value.
|
|
- (int32_t)increase;
|
|
@end
|
|
|
|
@implementation _YYTextSentinel {
|
|
int32_t _value;
|
|
}
|
|
- (int32_t)value {
|
|
return _value;
|
|
}
|
|
- (int32_t)increase {
|
|
return OSAtomicIncrement32(&_value);
|
|
}
|
|
@end
|
|
|
|
|
|
@implementation YYTextAsyncLayerDisplayTask
|
|
@end
|
|
|
|
|
|
@implementation YYTextAsyncLayer {
|
|
_YYTextSentinel *_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 = [_YYTextSentinel 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<YYTextAsyncLayerDelegate> delegate = (id)self.delegate;
|
|
YYTextAsyncLayerDisplayTask *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);
|
|
_YYTextSentinel *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(YYTextAsyncLayerGetReleaseQueue(), ^{
|
|
CFRelease(image);
|
|
});
|
|
}
|
|
if (task.didDisplay) task.didDisplay(self, YES);
|
|
CGColorRelease(backgroundColor);
|
|
return;
|
|
}
|
|
|
|
dispatch_async(YYTextAsyncLayerGetDisplayQueue(), ^{
|
|
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()) {
|
|
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) {
|
|
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
|