当前位置: 首页 > 工具软件 > YYKit > 使用案例 >

YYKit-YYAsyncLayer分析

哈朗
2023-12-01

1. YYAsyncLayer是什么?

YYAsyncLayer继承于CALayer的异步图层。

2. YYAsyncLayer的作用?

对于一些需要更好性能的试图,可以使用YYAsyncLayer实现异步绘制,优化试图性能。

3. YYAsyncLayer如何实现?

1. YYTransaction

YYTransaction是用于在主线程的runloop中添加observer,用于在runloop休眠之前执行相关方法。

我们查看下YYTransaction的源码:

//1. 创建一个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;
}

//2. 提交-往主线程的runloop中添加观察者
- (void)commit {
    if (!_target || !_selector) return;
    YYTransactionSetup();
    [transactionSet addObject:self];
}

//由于使用NSMutableSet存储自定义对象,而NSMutableSet使用HashMap实现存储结构的。hashmap中需要先寻找hash索引,以便快速的找到对应存储的对象。
- (NSUInteger)hash {
    long v1 = (long)((void *)_selector);
    long v2 = (long)_target;
    return v1 ^ v2;
}

//在hashmap中寻找对象的时候,当找到hash索引后,如果对应位置没有值,则直接存储,否则需要判断对象是否相等,如果相等则放弃存储,否则指定位置使用链表或者再次寻址。
- (BOOL)isEqual:(id)object {
    if (self == object) return YES;
    if (![object isMemberOfClass:self.class]) return NO;
    YYTransaction *other = object;
    return other.selector == _selector && other.target == _target;
}

其中我们可以看下YYTransactionSetup的实现:

static NSMutableSet *transactionSet = nil;

static void YYTransactionSetup() {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        //创建一个NSMutableSet对象,用于存储监听指定Activity的Transaction对象,以备回调顺序处理
        transactionSet = [NSMutableSet new];

        //获取主线程的runloop
        CFRunLoopRef runloop = CFRunLoopGetMain();
        CFRunLoopObserverRef observer;

        //创建观察者,注意CFIndex,这个参数指定相同activity对应的多个Transaction回调函数执行的优先级,0表示优先级最高,越往后优先级越低
        observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
                                           kCFRunLoopBeforeWaiting | kCFRunLoopExit,
                                           true,      // repeat
                                           0xFFFFFF,  // after CATransaction(2000000)--界面更新
                                           YYRunLoopObserverCallBack, NULL);

        CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
        CFRelease(observer);
    });
}

//回调函数分析
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
    }];
}
2. YYSentinel

暂时没有理解为什么要使用YYSentinel

3. YYAsyncLayer

YYAsyncLayer是CALayer的子类,用于异步渲染内容。

YYAsyncLayerDelegate是YYAsyncLayer的代理,存在如下代理方法:

- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask;

返回一个YYAsyncLayerDisplayTask对象

@interface YYAsyncLayerDisplayTask : NSObject

//异步绘制之前调用
@property (nullable, nonatomic, copy) void (^willDisplay)(CALayer *layer);

//绘制内容的时候调用
@property (nullable, nonatomic, copy) void (^display)(CGContextRef context, CGSize size, BOOL(^isCancelled)(void));

//在绘制完成后调用
@property (nullable, nonatomic, copy) void (^didDisplay)(CALayer *layer, BOOL finished);

@end

以下我们仔细分析一下YYAsyncLayer源码:

//初始化一个YYAsyncLayer对象
- (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 = [YYSentinel new];
    _displaysAsynchronously = YES;
    return self;
}

- (void)dealloc {
    [_sentinel increase];
}

//重写setNeedsDisplay方法
- (void)setNeedsDisplay {
    [self _cancelAsyncDisplay];
    [super setNeedsDisplay];
}

//重写display方法
- (void)display {
    //主要作用是什么?
    super.contents = super.contents;


    [self _displayAsync:_displaysAsynchronously];
}

- (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 { //同步绘制
        [_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();

        //设置显示的contents内容
        self.contents = (__bridge id)(image.CGImage);
        if (task.didDisplay) task.didDisplay(self, YES);
    }
}

//取消异步显示
- (void)_cancelAsyncDisplay {
    [_sentinel increase];
}

我们看下全局渲染队列:

//根据当前系统激活的处理器的数量与最大限制进程数一起设置队列最大的数,防止死锁或者线程之间切换的低效率
static dispatch_queue_t YYAsyncLayerGetDisplayQueue() {
#ifdef YYDispatchQueuePool_h
    return YYDispatchQueueGetForQOS(NSQualityOfServiceUserInitiated);
#else
#define MAX_QUEUE_COUNT 16 //设置最大的队列数为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 {
            for (NSUInteger i = 0; i < queueCount; i++) {
                queues[i] = dispatch_queue_create("com.ibireme.yykit.render", DISPATCH_QUEUE_SERIAL);
                dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
            }
        }
    });
    int32_t cur = OSAtomicIncrement32(&counter);
    if (cur < 0) cur = -cur;

    //返回对应的一个同步队列
    return queues[(cur) % queueCount];
#undef MAX_QUEUE_COUNT
#endif
}

static dispatch_queue_t YYAsyncLayerGetReleaseQueue() {
#ifdef YYDispatchQueuePool_h
    return YYDispatchQueueGetForQOS(NSQualityOfServiceDefault);
#else
    return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
#endif
}

4. YYAsyncLayer为什么这样设计?

1. YYAsyncLayer是参照了UIView的试图显示么?
  1. 我们先看一下通常的UIView试图,一旦创建UIView,一个CALayer自然被创建。而UIView是CALayer的delegate。当CALayer要显示的时候,CALayer会委托UIView提供要显示的内容,比如UIView中的drawRect等方法。而此时的UIView的内容都是在主线程中创建的。
  2. 对照YYAsyncLayer,我们在创建UIView的子类的时候,实现了:
+(CALayer *)layer
{
    return [YYAsyncLayer class];
}

这使得YYAsyncLayer成为了UIView的子类的layer层,而对应UIView的子类成为了YYAsyncLayer的delegate。当runloop中注册的试图渲染回调过来的时候,会触发display方法,而在重载的display中,我们调用了_displayAsync进行异步/同步绘制的函数。在这个函数中我们回调delegate中的相关绘制,最终将bitmap设置为layer的contents属性中用于内容显示操作。

2. 为什么要创建队列数组来实现异步操作,而不是在单一的一个队列或者在一个全局队列中实现异步操作?

在单核的手机系统中,通过时间片轮询的方式实现了异步的操作。但是对于时间片轮询,每个任务设置的时间片如果太大,同时任务比较多,这个时候位于末尾的任务可能等待的时间会比较久。如果每个任务设置的时间片过小,对于一个任务时间片用完,就得切换到新的任务并且给其设置时间片,这个切换过程是比较耗费cpu资源的。
当前较新的iphone手机都具备多核(iphone7:4核),这让多线程实现更加有效率。但是如果过多的分配多线程,在单核手机系统中出现的时间片轮询中环境切换耗费cpu资源问题同样存在。由于以上问题,我们也得控制并发线程数。

YYAsyncLayer中通过以下代码实现了控制并发线程数的目的:

static dispatch_queue_t YYAsyncLayerGetDisplayQueue() {

//对于实现了YYDispatchQueuePool,直接使用YYDispatchQueueGetForQOS获取
#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 {
            for (NSUInteger i = 0; i < queueCount; i++) {
                queues[i] = dispatch_queue_create("com.ibireme.yykit.render", DISPATCH_QUEUE_SERIAL);
                dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
            }
        }
    });
    int32_t cur = OSAtomicIncrement32(&counter);
    if (cur < 0) cur = -cur;

    //轮询返回对应的同步队列
    return queues[(cur) % queueCount];
#undef MAX_QUEUE_COUNT
#endif
}

以上代码通过设置最大的同步队列数组长度来控制最大并发数。在使用队列进行异步操作中:
dispatch_async(queue,^{

   异步操作 
});
其中queue是通过以上代码中同步队列数组轮询返回一个同步队列,然后异步执行同步队列,其中同步队列中的任务是同步执行的。


除了通过异步同步队列之外,我们也可以通过锁的方式(信号量)实现控制并发数

self.semaphore = dispatch_semaphore_create(3);
    __weak typeof(self) weakSelf = self;
    for (int i = 0 ; i < 1000; i ++ ) {

         dispatch_semaphore_wait(weakSelf.semaphore, 3000);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{

            dispatch_semaphore_wait(weakSelf.semaphore, 3000);
        });
    }

3. 使用atomic_fetch_add_explicit(原子操作)有什么特殊含义?

暂时未理解在项目中使用原子操作的原因

4. YYTransaction为什么要在CATransaction提交渲染后在执行setNeedsDisplay方法呢?

CALayer的display方法调用是在UIView显示调用了setNeedDisplay或者setNeedDisplayInRect之后调用的。而YYAsyncLayer的异步绘制也在display中执行。

 类似资料: