美文网首页
『ios』YYDispatchQueuePool源码

『ios』YYDispatchQueuePool源码

作者: butterflyer | 来源:发表于2018-10-31 15:46 被阅读120次
    image.png

    以前所有的东西都不过是为了现在所做的沉淀。

    YYDispatchQueuePool.h文件的初始化方法

    - (instancetype)initWithName:(nullable NSString *)name queueCount:(NSUInteger)queueCount qos:(NSQualityOfService)qos;
    
    /// Pool's name.
    @property (nullable, nonatomic, readonly) NSString *name;
    
    /// Get a serial queue from pool.
    - (dispatch_queue_t)queue; 
    
    + (instancetype)defaultPoolForQOS:(NSQualityOfService)qos;
    

    YYDispatchQueuePool.m

    从init方法开始看

    首先定义了一个结构体。

    typedef struct {
        const char *name; //上下文环境名称
        void **queues;  //队列数组指针
        uint32_t queueCount; //同步队列的个数
        int32_t counter; //计数器
    } YYDispatchContext;
    

    这个结构体是贯穿全局的关键。

    第一种获取线程队列的方法

    然后是init方法

    - (instancetype)initWithName:(NSString *)name queueCount:(NSUInteger)queueCount qos:(NSQualityOfService)qos {
        if (queueCount == 0 || queueCount > MAX_QUEUE_COUNT) return nil;
        self = [super init];
        _context = YYDispatchContextCreate(name.UTF8String, (uint32_t)queueCount, qos);
        if (!_context) return nil;
        _name = name;
        return self;
    }
    

    YYDispatchContextCreate创建 YYDispatchContext

    
    static YYDispatchContext *YYDispatchContextCreate(const char *name,
                                                     uint32_t queueCount,
                                                     NSQualityOfService qos) {
        YYDispatchContext *context = calloc(1, sizeof(YYDispatchContext));//开辟空间
        if (!context) return NULL;
        context->queues =  calloc(queueCount, sizeof(void *));//为什么用->呢?是因为访问结构体需要这么访问,类似于点语法
        if (!context->queues) {
            free(context);
            return NULL;
        }
        if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
            dispatch_qos_class_t qosClass = NSQualityOfServiceToQOSClass(qos);//获取优先级
            for (NSUInteger i = 0; i < queueCount; i++) {
                dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, qosClass, 0);
                dispatch_queue_t queue = dispatch_queue_create(name, attr);//目的是为了指定队列的优先级
                context->queues[i] = (__bridge_retained void *)(queue);//__bridge_retained 是为了将Object-C对象转化为Core Foundation对象,同时让队列持有该对象
            }
        } else {
            long identifier = NSQualityOfServiceToDispatchPriority(qos);
            for (NSUInteger i = 0; i < queueCount; i++) {
                dispatch_queue_t queue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL);
                dispatch_set_target_queue(queue, dispatch_get_global_queue(identifier, 0));//目的是为了指定队列的优先级
                context->queues[i] = (__bridge_retained void *)(queue);
            }
        }
        context->queueCount = queueCount;
        if (name) {
             context->name = strdup(name);//strdup可以直接把要复制的内容复制给没有初始化的指针,因为它会自动分配空间给目的指针,需要手动free()进行内存回收。 
        }
        return context;
    }
    

    下面是释放的方法。

    - (void)dealloc {
        if (_context) {
            YYDispatchContextRelease(_context);
            _context = NULL;
        }
    }
    static void YYDispatchContextRelease(YYDispatchContext *context) {
        if (!context) return;
        if (context->queues) {
            for (NSUInteger i = 0; i < context->queueCount; i++) {
                void *queuePointer = context->queues[i];
                dispatch_queue_t queue = (__bridge_transfer dispatch_queue_t)(queuePointer);//__bridge_transfer自动释放
                const char *name = dispatch_queue_get_label(queue);
                if (name) strlen(name); // avoid compiler warning
                queue = nil;
            }
            free(context->queues);
            context->queues = NULL;
        }
        if (context->name) free((void *)context->name);
    }
    

    第二种获取队列的方法

    默认根据队列优先级获取队列的方法

    + (instancetype)defaultPoolForQOS:(NSQualityOfService)qos {
        switch (qos) {
            case NSQualityOfServiceUserInteractive: {
                static YYDispatchQueuePool *pool;
                static dispatch_once_t onceToken;
                dispatch_once(&onceToken, ^{
                    pool = [[YYDispatchQueuePool alloc] initWithContext:YYDispatchContextGetForQOS(qos)];
                });
                return pool;
            } break;
            case NSQualityOfServiceUserInitiated: {
                static YYDispatchQueuePool *pool;
                static dispatch_once_t onceToken;
                dispatch_once(&onceToken, ^{
                    pool = [[YYDispatchQueuePool alloc] initWithContext:YYDispatchContextGetForQOS(qos)];
                });
                return pool;
            } break;
            case NSQualityOfServiceUtility: {
                static YYDispatchQueuePool *pool;
                static dispatch_once_t onceToken;
                dispatch_once(&onceToken, ^{
                    pool = [[YYDispatchQueuePool alloc] initWithContext:YYDispatchContextGetForQOS(qos)];
                });
                return pool;
            } break;
            case NSQualityOfServiceBackground: {
                static YYDispatchQueuePool *pool;
                static dispatch_once_t onceToken;
                dispatch_once(&onceToken, ^{
                    pool = [[YYDispatchQueuePool alloc] initWithContext:YYDispatchContextGetForQOS(qos)];
                });
                return pool;
            } break;
            case NSQualityOfServiceDefault:
            default: {
                static YYDispatchQueuePool *pool;
                static dispatch_once_t onceToken;
                dispatch_once(&onceToken, ^{
                    pool = [[YYDispatchQueuePool alloc] initWithContext:YYDispatchContextGetForQOS(NSQualityOfServiceDefault)];
                });
                return pool;
            } break;
        }
    }
    
    NSQualityOfServiceUserInteractive:最高优先级, 用于处理 UI 相关的任务
    NSQualityOfServiceUserInitiated:次高优先级, 用于执行需要立即返回的任务
    NSQualityOfServiceUtility:普通优先级,主要用于不需要立即返回的任务
    NSQualityOfServiceBackground:后台优先级,用于处理一些用户不会感知的任务
    NSQualityOfServiceDefault:默认优先级,当没有设置优先级的时候,线程默认优先级
    

    第三种获取队列的方法

    dispatch_queue_t YYDispatchQueueGetForQOS(NSQualityOfService qos) {
        return YYDispatchContextGetQueue(YYDispatchContextGetForQOS(qos));
    }
    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);
    }
    static YYDispatchContext *YYDispatchContextGetForQOS(NSQualityOfService qos) {
        static YYDispatchContext *context[5] = {0};
        switch (qos) {
            case NSQualityOfServiceUserInteractive: {
                static dispatch_once_t onceToken;
                dispatch_once(&onceToken, ^{
                    int count = (int)[NSProcessInfo processInfo].activeProcessorCount;//获取当前活跃的线程数
                    count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
                    context[0] = YYDispatchContextCreate("com.ibireme.yykit.user-interactive", count, qos);
                });
                return context[0];
            } break;
            case NSQualityOfServiceUserInitiated: {
                static dispatch_once_t onceToken;
                dispatch_once(&onceToken, ^{
                    int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
                    count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
                    context[1] = YYDispatchContextCreate("com.ibireme.yykit.user-initiated", count, qos);
                });
                return context[1];
            } break;
            case NSQualityOfServiceUtility: {
                static dispatch_once_t onceToken;
                dispatch_once(&onceToken, ^{
                    int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
                    count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
                    context[2] = YYDispatchContextCreate("com.ibireme.yykit.utility", count, qos);
                });
                return context[2];
            } break;
            case NSQualityOfServiceBackground: {
                static dispatch_once_t onceToken;
                dispatch_once(&onceToken, ^{
                    int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
                    count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
                    context[3] = YYDispatchContextCreate("com.ibireme.yykit.background", count, qos);
                });
                return context[3];
            } break;
            case NSQualityOfServiceDefault:
            default: {
                static dispatch_once_t onceToken;
                dispatch_once(&onceToken, ^{
                    int count = (int)[NSProcessInfo processInfo].activeProcessorCount;
                    count = count < 1 ? 1 : count > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : count;
                    context[4] = YYDispatchContextCreate("com.ibireme.yykit.default", count, qos);
                });
                return context[4];
            } break;
        }
    }
    

    下面这段是yykit作者的原话
    大量的任务提交到后台队列时,某些任务会因为某些原因(此处是 CGFont 锁)被锁住导致线程休眠,或者被阻塞,concurrent queue 随后会创建新的线程来执行其他任务。当这种情况变多时,或者 App 中使用了大量 concurrent queue 来执行较多任务时,App 在同一时刻就会存在几十个线程同时运行、创建、销毁。CPU 是用时间片轮转来实现线程并发的,尽管 concurrent queue 能控制线程的优先级,但当大量线程同时创建运行销毁时,这些操作仍然会挤占掉主线程的 CPU 资源。ASDK 有个 Feed 列表的 Demo:SocialAppLayout,当列表内 Cell 过多,并且非常快速的滑动时,界面仍然会出现少量卡顿,我谨慎的猜测可能与这个问题有关。

    使用 concurrent queue 时不可避免会遇到这种问题,但使用 serial queue 又不能充分利用多核 CPU 的资源。我写了一个简单的工具 YYDispatchQueuePool,为不同优先级创建和 CPU 数量相同的 serial queue,每次从 pool 中获取 queue 时,会轮询返回其中一个 queue。我把 App 内所有异步操作,包括图像解码、对象释放、异步绘制等,都按优先级不同放入了全局的 serial queue 中执行,这样尽量避免了过多线程导致的性能问题。

    从话里应该可以读出作者的用意。

    我的理解,每个队列都是串行队列,串行队列异步执行会创建一个线程,从源码中可以看到作者是复用了之前的队列,这样就不会造成并行队列所产生的问题。

    涉及知识点

    __bridge_retained是在桥接后让Core Foundation对象变量持有对象,即让对象引用计数+1,__bridge_transfer桥接后让Core Foundation对象变量释放所持有的对象,即让对象引用计数-1。而__bridge除了桥接其他什么操作都不做。

    访问对象的属性时用了 "->" 是因为 C语言 语法规定, 在通过指针来访问结构体变量时, 若想访问结构体变量中的属性, 要用 "->" 来访问, 这也从侧面说明了 OC 中的类本质也是结构体

    strdup可以直接把要复制的内容复制给没有初始化的指针,因为它会自动分配空间给目的指针,需要手动free()进行内存回收。
    strcpy的目的指针一定是已经分配(足够)内存的指针

    想要找一群一起进步的人,请加入我

    image.png

    相关文章

      网友评论

          本文标题:『ios』YYDispatchQueuePool源码

          本文链接:https://www.haomeiwen.com/subject/pxeatqtx.html