YYAsyncLayer
是进行视图异步渲染的一个库。它的主要实现思路是
- 替换原有的
UIView
视图的CALayer
类为YYAsyncLayer
。 - 在
YYAsyncLayer
里面的绘制方法里,创建异步绘制的上下文进行异步绘制,将具体的视图绘制外包给原有的视图去做,最后在异步绘制得到 Image 图片,通过主线程赋值给CALayer
的contents
属性。 - 绘制的时机还是通过观察 Runloop 对应的时间节点,在指定的时间节点,将提交给数组中的绘制任务完成。
# YYAsyncLayer 使用方式
直接贴了 YYAsyncLayer
的使用方式
@interface YYLabel : UIView
@property NSString *text;
@end
@implementation YYLabel
- (void)setText:(NSString *)text {
_text = text.copy;
[[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];
}
- (void)contentsNeedUpdated { [self.layer setNeedsDisplay]; }
#pragma mark - YYAsyncLayer
+ (Class)layerClass { return YYAsyncLayer.class; }
- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {
// capture current state to display task
NSString *text = _text;
YYAsyncLayerDisplayTask *task = [YYAsyncLayerDisplayTask new];
task.willDisplay = ^(CALayer *layer) {
//...
};
task.display = ^(CGContextRef context, CGSize size, BOOL(^isCancelled)(void)) {
//绘制任务
};
task.didDisplay = ^(CALayer *layer, BOOL finished) {
//
};
return task;
}
@end
- 自定制类复写
+(Class)layerClass
方法,替换原有默认的CALayer
类为库中的YYAsyncLayer
类。 - 实现协议方法
- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask
返回异步绘制任务类YYAsyncLayerDisplayTask
。这个类中最关键的就是 display 回调方法,就是在这个方法里面完成了提交给子线程的异步绘制任务。 - 当修改原始视图内容的时候,通过
YYTransaction
类提交修改任务,最终调用YYAsyncLayer
的display
方法
# YYAsyncLayer
_displayAsync
方法说明
YYAsyncLayer
类中 _displayAsync
方法是具体异步绘制核心的方法,主要就是从代理视图获取到具体的绘制任务实例,用作 YYAsyncLayer
绘制的回调方法。
该方法简单实现如下:
YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
task.willDisplay(self);
dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
task.display(context, size, isCancelled);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.contents = (__bridge id)(image.CGImage);
if (task.didDisplay) task.didDisplay(self, YES);
});
});
在子线程中通过 UIGraphicsBeginImageContextWithOptions
创建一个基于位图的绘图上下文,然后拿到该绘图上下文之后,回调给代理(通常是视图)层去完成具体的绘制操作,最后将绘制好的内容通过上下文转成图片,在回调的主线程将图片内容赋值给 Layer 的 contents
属性。
这个绘制的过程和 CoreAnimation 进行绘制的流程几乎一模一样,在 CoreAnimation 的绘制过程中也是 CA 负责创建 backing store (我理解其实也是上下文的意思),然后再 UIView
的 drawRect:
方法中直接获取上下文,进行绘制。所以 task 的 display 方法和 UIView
的 drawRect:
方法本质上一模一样。只不过是 task 的 display 方法是在一个异步的线程中执行的,当然这也就是 YYAsyncLayer
库实现的目的。
YYAsyncLayerGetDisplayQueue 方法说明
这个方法用来获取渲染线程队列。作者实现这个方法的原因是「使用 concurrent queue 时不可避免会遇到这种问题,但使用 serial queue 又不能充分利用多核 CPU 的资源」,所以创建多个串行队列,来供渲染时取用。
这里的关键是,设置多少个串行队列?YY 给出的答案是根据系统当前活跃的 CPU 核数 activeProcessorCount
,同时每次取队列池中的队列都保证和上一次渲染时的串行队列不同,这样便能利用多核的 CPU 资源,有点类似线程池,但是队列和线程并不是一个意思。
queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
而且串行队列的优先级都是 user interactive,表示线队列的优先级还是挺高的。
user interactive 用户交互的任务,通常和UI有关
# YYTransaction
这个类显然是参考了 CA:Transaction
的命名。YYTransaction
的主要职责是存储不同视图绘制任务,并且在给定的时间节点去触发绘制任务。
首先在初始化配置方法里去创建 Runloop 观察者,观察 kCFRunLoopBeforeWaiting
, kCFRunLoopExit
这两个时间节点,并配置对应的触发方法 YYRunLoopObserverCallBack
static NSMutableSet *transactionSet = nil;
static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
if (transactionSet.count == 0) return;
NSSet *currentSet = transactionSet;
transactionSet = [NSMutableSet new];
[currentSet enumerateObjectsUsingBlock:^(YYTransaction *transaction, BOOL *stop) {
[transaction.target performSelector:transaction.selector];
}];
}
static void YYTransactionSetup() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
transactionSet = [NSMutableSet new];
CFRunLoopRef runloop = CFRunLoopGetMain();
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopBeforeWaiting | kCFRunLoopExit, true, 0xFFFFFF, YYRunLoopObserverCallBack, NULL);
CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
CFRelease(observer);
});
}
当视图提交修改后,想要触发的话,视图就通过下面的方法,将绘制任务提交到全局 transactionSet
数组中,在上面的 Runloop 中取出数组中存储的 YYTransaction
实例,并执行里面存储的绘制任务。绘制任务当然最终还是要触发到 YYAsyncLayer
的 _displayAsync
方法。
@implementation YYTransaction
+ (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector{
if (!target || !selector) return nil;
YYTransaction *t = [YYTransaction new];
t.target = target;
t.selector = selector;
return t;
}
@end
# YYSentinel
本质上就是操作一个全局的计数器。不在 YYAsyncLayer
中直接使用静态变量的原因,我感觉还是出于线程安全的考虑。
基本上就是这些吧,感觉 YY 大神真的对系统绘制的这套东西门清,才能写出和系统绘制思路如此相似的类,牛逼。
参考地址: