开源库信息
源码解析
我们在分析代码之前先看下YYAsyncLayer应用层的用法,下面是官网上给出的一个例子:
@implementation YYLabel
- (void)setText:(NSString *)text { _text = text.copy; [[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit]; }
- (void)layoutSubviews { [super layoutSubviews]; [[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit]; }
- (void)contentsNeedUpdated { [self.layer setNeedsDisplay]; }
#pragma mark - YYAsyncLayer
+ (Class)layerClass { return YYAsyncLayer.class; }
- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {
YYAsyncLayerDisplayTask *task = [YYAsyncLayerDisplayTask new]; task.willDisplay = ^(CALayer *layer) { };
task.display = ^(CGContextRef context, CGSize size, BOOL(^isCancelled)(void)) { if (isCancelled()) return; 这里完成界面的绘制 };
task.didDisplay = ^(CALayer *layer, BOOL finished) { if (finished) { } else { } }; return task; } @end
|
在每个需要更新界面的地方,都需要调用:
[[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];
|
contentsNeedUpdated方法如下:
- (void)contentsNeedUpdated { [self.layer setNeedsDisplay]; }
|
它会调用setNeedsDisplay触发layer的绘制。而我们要绘制的内容都放在newAsyncDisplayTask方法中,在newAsyncDisplayTask方法中返回一个新建的YYAsyncLayerDisplayTask。在YYAsyncLayerDisplayTask的willDisplay,display,didDisplay中分别覆写对应的block在整个绘制过程中插入操作:
- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {
YYAsyncLayerDisplayTask *task = [YYAsyncLayerDisplayTask new]; task.willDisplay = ^(CALayer *layer) { };
task.display = ^(CGContextRef context, CGSize size, BOOL(^isCancelled)(void)) { if (isCancelled()) return; 这里完成界面的绘制 };
task.didDisplay = ^(CALayer *layer, BOOL finished) { if (finished) { } else { } }; return task; } @end
|
YYAsyncLayer代码量不多,我们下面就顺着上面的例子过下整个源码,整个过程分成两部分:YYTransaction的提交,以及异步绘制过程。
1.YYTransaction的提交
首先是YYTransaction的创建,每个YYTransaction代表一个提交的操作,它有两个属性target,selector,这个不用过多解释吧,用于指定当前提交的操作是哪个对象中的哪个操作。
+ (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector{ if (!target || !selector) return nil; YYTransaction *t = [YYTransaction new]; t.target = target; t.selector = selector; return t; }
|
我们最关键的是要了解这些操作是提交到哪里?会在什么时候触发。我们继续往下看:
- (void)commit { if (!_target || !_selector) return; YYTransactionSetup(); [transactionSet addObject:self]; }
static void YYTransactionSetup() { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ transactionSet = [NSMutableSet new]; CFRunLoopRef runloop = CFRunLoopGetMain(); CFRunLoopObserverRef observer; observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopBeforeWaiting | kCFRunLoopExit, true, 0xFFFFFF, YYRunLoopObserverCallBack, NULL); CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes); CFRelease(observer); }); }
|
看过之前介绍RunLoop的文章会对上面的代码比较熟悉,YYTransactionSetup方法中,向主RunLoop中注册了一个Observer,在主RunLoop 进入休眠之前(kCFRunLoopBeforeWaiting)以及 主RunLoop退出(kCFRunLoopExit)之前会回调YYRunLoopObserverCallBack,这个observer的优先级为0xFFFFFF,CATransaction为2000000,也就是在CATransaction之后。我们看YYRunLoopObserverCallBack中的任务吧:
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) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [transaction.target performSelector:transaction.selector]; #pragma clang diagnostic pop }]; }
|
YYRunLoopObserverCallBack 方法中会遍历transactionSet中每个YYTransaction,通过performSelector执行提交的selector。
也就是在主RunLoop进入休眠,退出之前会将遍历当前提交的所有YYTransaction,执行里面的selector。在上面的例子中我们在selector中调用了[self.layer setNeedsDisplay],也就是启动了layer的绘制,我们来看下绘制过程。
2.异步绘制过程
在UIKit 中界面渲染是发生在主线程的,我们来看下YYAsyncLayer的异步绘制过程:
我们上面提到了在主RunLoop进入休眠,退出之前会将遍历当前提交的所有YYTransaction,执行里面的selector从而调用[self.layer setNeedsDisplay],在YYAsyncLayer方法中覆写了setNeedsDisplay,它在调用父类的setNeedsDisplay之前,会调用****_cancelAsyncDisplay****:
- (void)setNeedsDisplay { [self _cancelAsyncDisplay]; [super setNeedsDisplay]; }
|
_cancelAsyncDisplay 只有一行代码:
- (void)_cancelAsyncDisplay { [_sentinel increase]; }
|
_sentinel其实是用来标记每次操作,增加了这个值之前的所有操作都会失效。在绘制代码中有如下的一行代码:
YYSentinel *sentinel = _sentinel; int32_t value = sentinel.value; BOOL (^isCancelled)(void) = ^BOOL() { return value != sentinel.value; };
|
所以每次调用_cancelAsyncDisplay 就会导致 value != sentinel.value。从而isCancelled是YES,就会取消过期的操作。
我们继续往下看,在[super setNeedsDisplay]之后,会触发绘制过程:
- (void)display { super.contents = super.contents; [self _displayAsync:_displaysAsynchronously]; }
|
display方法中会调用_displayAsync:
- (void)_displayAsync:(BOOL)async { __strong id<YYAsyncLayerDelegate> delegate = (id)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)(void) = ^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(YYAsyncLayerGetReleaseQueue(), ^{ CFRelease(image); }); } if (task.didDisplay) task.didDisplay(self, YES); CGColorRelease(backgroundColor); return; } dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{ 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 { } }
|
_displayAsync 代码很长我们只看异步绘制的情况:
__strong id<YYAsyncLayerDelegate> delegate = (id)self.delegate; YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
|
在_displayAsync的开始会调用上层的newAsyncDisplayTask,来创建一个YYAsyncLayerDisplayTask
接下来就进行异步绘制:
dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
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); UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); 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); } }); });
|
上面的代码中绘制部分都放在YYAsyncLayerGetDisplayQueue队列中完成,具体绘制的部分看上面的注释,最后在dispatch_get_main_queue队列中将绘制的界面image设置到self.contents。
那YYAsyncLayerGetDisplayQueue又是怎么组织的呢?
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 { } }); int32_t cur = OSAtomicIncrement32(&counter); if (cur < 0) cur = -cur; return queues[(cur) % queueCount]; #undef MAX_QUEUE_COUNT #endif }
|
这里其实可以使用YYDispatchQueuePool,但是我们这里先看下不使用YYDispatchQueuePool的情景。上面代码中创建了和处理器数量相同的串行queues。每次会循环从这些队列中取出一个queues,在上面完成绘制,这样的好处是为了避免某些时候某些线程卡住了,导致不断为后来任务不断创建新的线程,导致线程爆炸。这个问题会在YYDispatchQueuePool中进行详细介绍。