YYAsyncLayer主要作用是异步绘图,可以提高性能,平常应用时,比如像微信朋友圈的列表内容就可以用这个框架实现。先看个例子:
YYTextShadow *shadow = [YYTextShadow new];//阴影
shadow.offset = CGSizeMake(0,1);
shadow.color = [UIColor blackColor];
shadow.radius = 5;
NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc]initWithString:@"Shadow"];
attributedStr.textShadow = shadow;
YYLabel *label = [YYLabel new];
label.attributedText = attributedStr;
[self.view addSubview: label];
-
YYAsyncLayer
继承于CALayer
,绘图时调用display
,通过子类重写CALayer
实现自定义绘制:
@interface YYAsyncLayer : CALayer
@implementation YYAsyncLayer
...
- (void)display {
super.contents = super.contents;
[self _displayAsync:_displaysAsynchronously];//_displaysAsynchronously默认YES
}
- (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);//准备绘制的工作
...
//子线程
dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
...
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);//开始合成图片
...
task.display(context, size, isCancelled);//调用绘制
...
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();//使用图片达到效果,可以减少图层的层级
...
//主线程
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);//绘制完成后的工作
}
});
});
}
- 然后调用代理协议,而代理就是
YYLabel
:
@implementation YYLabel
...
- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {
...
YYAsyncLayerDisplayTask *task = [YYAsyncLayerDisplayTask new];
task.willDisplay = ^(CALayer *layer) { ... }
task.display = ^(CGContextRef context, CGSize size, BOOL (^isCancelled)(void)) {
...
[drawLayout drawInContext:context size:size point:point view:nil layer:nil debug:debug cancel:isCancelled];//绘制
};
task.didDisplay = ^(CALayer *layer, BOOL finished) { ... }
return task;
}
@implementation YYTextLayout
...
- (void)drawInContext:(CGContextRef)context
size:(CGSize)size
point:(CGPoint)point
view:(UIView *)view
layer:(CALayer *)layer
debug:(YYTextDebugOption *)debug
cancel:(BOOL (^)(void))cancel{
@autoreleasepool {
...
if (self.needDrawShadow && context) {
if (cancel && cancel()) return;
YYTextDrawShadow(self, context, size, point, cancel);//绘制阴影
}
...
}
}
- 完成后回到第1步,返回主线程进行显示
self.contents = (__bridge id)(image.CGImage);
。
使用框架创建任务时,会根据
Runloop
状态分发任务,然后会调用setNeedsDisplay
,接着执行display
进行异步绘制。最终显示的文字内容其实是UIImage
。
- YYAsyncLayerGetDisplayQueue
在上文出现了YYAsyncLayerGetDisplayQueue
,用它来调度子线程,内部进行了处理:
static dispatch_queue_t YYAsyncLayerGetDisplayQueue() {
#ifdef YYDispatchQueuePool_h
return YYDispatchQueueGetForQOS(NSQualityOfServiceUserInitiated);
#else
...
}
dispatch_queue_t YYDispatchQueueGetForQOS(NSQualityOfService qos) {
return YYDispatchContextGetQueue(YYDispatchContextGetForQOS(qos));
}
通过YYDispatchContextGetForQOS
,根据NSQualityOfService
控制最大线程数,防止线程数使用过大:
static YYDispatchContext *YYDispatchContextGetForQOS(NSQualityOfService qos) {
static YYDispatchContext *context[5] = {0};
switch (qos) {
case NSQualityOfServiceUserInteractive: {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
context[0] = YYDispatchContextCreate("com.ibireme.yykit.user-interactive", count, qos);
});
return context[0];
} break;
case NSQualityOfServiceUserInitiated: { ... } break;
case NSQualityOfServiceUtility: { ... } break;
case NSQualityOfServiceBackground: { ... } break;
default: { ... } break;
}
}
并可以对已经创建过的队列进行复用,从而减少占用 CPU 资源:
static dispatch_queue_t YYDispatchContextGetQueue(YYDispatchContext *context) {
int32_t counter = OSAtomicIncrement32(&context->counter);
if (counter < 0) counter = -counter;
void *queue = context->queues[counter % context->queueCount];//获取已经创建过的队列进行复用,减少cpu资源占用
return (__bridge dispatch_queue_t)(queue);
}