开源库信息
源码解析

我们在分析代码之前先看下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) {
// finished
} else {
// cancelled
}
};
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) {
// finished
} else {
// cancelled
}
};
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, // repeat
0xFFFFFF, // after CATransaction(2000000)
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);
}
//将绘制上下文传递出去调用YYAsyncLayerDisplayTask的display方法在上下文进行绘制
task.display(context, size, isCancelled);
//.........
//取出task.display绘制的图片image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//........
dispatch_async(dispatch_get_main_queue(), ^{
if (isCancelled()) {
if (task.didDisplay) task.didDisplay(self, NO);
} else {
//在主线程中将image内容设置到self.contents
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) {
// 比如我们电脑是四核的那么就创建四个串行dispatch_queue。
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;
//每次在取队列的时候就会从queues[0],queues[1],queues[2],queues[3] 选择一个串行队列返回,在上面完成绘制。
return queues[(cur) % queueCount];
#undef MAX_QUEUE_COUNT
#endif
}

这里其实可以使用YYDispatchQueuePool,但是我们这里先看下不使用YYDispatchQueuePool的情景。上面代码中创建了和处理器数量相同的串行queues。每次会循环从这些队列中取出一个queues,在上面完成绘制,这样的好处是为了避免某些时候某些线程卡住了,导致不断为后来任务不断创建新的线程,导致线程爆炸。这个问题会在YYDispatchQueuePool中进行详细介绍。

Contents
  1. 1. 开源库信息
  2. 2. 源码解析
  3. 3. 1.YYTransaction的提交
  4. 4. 2.异步绘制过程