关于 YYAsyncLayer 可能出现的问题

先上YYAsyncLayer 源码链接

问题提出

在看@indulge_in的YYAsyncLayer 源码剖析:异步绘制时看到:

要点 3 :轮询返回队列

使用原子自增函数OSAtomicIncrement32()对局部静态变量counter进行自增,然后通过取模运算轮询返回队列。

注意这里使用了一个判断:if (cur < 0) cur = -cur,当cur自增越界时就会变为负数最大值(在二进制层面,是用正整数的反码加一来表示其负数的)。

if (cur < 0) cur = -cur开始是觉得很巧妙,使用OSAtomicIncrement32()自增来均匀地取队列数组里面的队列,但是仔细一想,发现了一个问题。

cur的数据类型是int32_t,所以当自增到越界的时候就会变为负数的最大值,而我们知道,正数的最大值为2147483647,而负数的最大值为-2147483648,绝对值不一样是因为正数有0。

所以当cur自增越界时就会变为负数最大值-2147483648,而这时候直接通过if (cur < 0) cur = -cur;这个判断来取正肯定是有问题的,因为int32_t类型不存在2147483648

问题验证

cur值到底会变成什么值呢,这我就写了一个验证代码:

static int counter = INT32_MAX;

int cur = OSAtomicIncrement32(&counter);
if (cur < 0) {
    cur = -cur;
}
NSLog(@" %d", cur);

结果打印输出是还是-2147483648,我推测是因为无法置为负值,所以不变。如果有更好的答案,可以评论一下,谢谢。

再看源码:

if (cur < 0) cur = -cur;
return queues[(cur) % queueCount];

cur取正失败则会导致取余以后的数的值也可能出现负值,因为-2147483648是2的31次方,如果queueCount不是2的倍数,则取余的值就不是0,而出现负值。

而根据源码:

queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;

而经过查阅,A11 和 A12 芯片 CPU 都是6核心并且能所有核心同时工作,所以有风险会出现余数为负值。

下面通过简单改写源码验证负值情况:

static dispatch_queue_t YYAsyncLayerGetDisplayQueue() {
#define MAX_QUEUE_COUNT 16
    static int queueCount = 6;
    static dispatch_queue_t queues[MAX_QUEUE_COUNT];
    static dispatch_once_t onceToken;
    static int32_t counter = INT32_MAX - 5;
    dispatch_once(&onceToken, ^{
        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);
        }
    });
    int32_t cur = OSAtomicIncrement32(&counter);
    if (cur < 0) cur = -cur;
    int remainder = cur%queueCount;
    NSLog(@" %d", remainder);
    return queues[remainder];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        for (NSInteger i = 0; i < 10; i++) {
            dispatch_queue_t queue = YYAsyncLayerGetDisplayQueue();
            dispatch_async(queue, ^{
                NSLog(@"");
            });
        }
    }
    return 0;
}

这段代码的结果是,当cur自增越界为-2147483648时,取余后的余数为-2,而此时调用YYAsyncLayerGetDisplayQueue返回queue时出现EXC_BAD_ACCESS崩溃。

问题解决

下面来讲一下如何解决这个问题。在发现这个问题之后我就去找了 YYDispatchQueuePool 库看看是否有同样的问题,因为YYAsyncLayer也是在本地没有YYDispatchQueuePool的情况下才使用这段代码。结果是并没有这个问题,先看代码:

static dispatch_queue_t YYDispatchContextGetQueue(YYDispatchContext *context) {
    uint32_t counter = (uint32_t)OSAtomicIncrement32(&context->counter);
    void *queue = context->queues[counter % context->queueCount];
    return (__bridge dispatch_queue_t)(queue);
}

YYDispatchQueuePoolOSAtomicIncrement32()自增调用后会强制转换为uint32_t所有就完全不会出现负值的情况,就算自增越界后也会变为0。所以counter不会出现负值,余数也不会出现负值,就完全没有以上的问题了。所以,只需要参照YYDispatchQueuePool将代码:

int32_t cur = OSAtomicIncrement32(&counter);
if (cur < 0) cur = -cur;

改为

uint32_t cur = (uint32_t)OSAtomicIncrement32(&counter);

就可以解决此问题。

已经提交 PullRequest https://github.com/ibireme/YYAsyncLayer/pull/21

你可能感兴趣的:(关于 YYAsyncLayer 可能出现的问题)