美文网首页
3、GCD-Objc - Part 1

3、GCD-Objc - Part 1

作者: Laughingg | 来源:发表于2016-06-21 10:20 被阅读85次

    GCD 两个核心概念:

    • 队列: 用来存放任务
    • 任务: 执行什么操作, 任务是使用 block 封装的代码块.

    GCD会自动将队列中的任务取出,放到对应的线程中执行.
    任务的取出遵循队列的FIFO原则:先进先出,后进后出

    GCD 有两个执行任务的函数:

    
    // 同步执行
    void
    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
    
    // 异步执行
    void
    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    
    两个函数只有两个参数:
    1. dispatch_queue_t queue  : 调度的队列
    2. dispatch_block_t block : 调度的任务
    
    GCD 使用的基本思路是: 
    1. 创建队列
    2. 创建任务
    3. 将任务添加到队列
    

    这两个函数和上面是对应的。 上面的函数是下面函数的封装。实际使用的还是下面的函数。由于使用起来比较麻烦,基本不怎么使用。

    void
    dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
    
    void
    dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
    
    /*
      参数:
        - dispatch_queue_t queue:       要添加函数的队列
        -  void *context:               传递到函数中的参数
        - dispatch_function_t work    : 队列中药执行的函数
    */ 
    
    typedef void (^dispatch_block_t) (void);         // 匿名函数(无参数无返回值)
    typedef void (*dispatch_function_t) (void *);    // 函数的指针
    

    队列的类型
    (可以从函数的类型中看出)

    • 并发队列(Concurrent Dispatch Queue): 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务), 并发功能只有在异步(dispatch_async)函数下才有效

    • 串行队列(Serial Dispatch Queue): 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务

    队列中任务的执行方式
    同步和异步的区别:

    • 同步:在当前线程中执行,当前代码不执行完,就不能够执行下一条代码。会阻塞当前线程。

    • 异步:在另一条线程中执行(不用等待当前代码执行完,就能够执行下一条),不会阻塞当前线程。

    1、常见的队列:

    主队列
    默认是串行队列,队列中的任务一次只能执行一个。
    (主队列中只有一个线程,主线程)
    可以

    // 队列的获取
    dispatch_queue_t q = dispatch_get_main_queue();
    
    
    // 函数的声明
    dispatch_queue_t
    dispatch_get_main_queue(void)
    {
        return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q);
    }
    

    全局队列:
    默认是并发队列。
    全局队列是为了方便 GCD 使用,苹果默认提供的。我们值需要获取。
    全局本身就是并发的。
    全局队列 apple 也会使用。更具服务质量,我们有四个类型的全局队列可以使用。

    // 队列的获取
    // 参数一般默认填 0, 0
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    
    
    
    // 函数的声明
    /*
    全局队列函数参数说明:
        * 第一个参数: dispatch_queue_priority_tpriority  队列的优先级
           *  - DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED   // 高优先级, 默认值为 2
           *  - DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT          // 默认优先级, 默认值为 0
           *  - DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY          // 低优先级, 默认值为 -2
           *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND       // 后台优先级
    
        *  unsigned longflags : 队列的标记, 默认填 0 ,标记的结果就会返回 null 值
           flags : 标记, 一般都是为了后期使用, 主要是用来区分队列.
    
        (为了使线程操作不那么的复杂,我们一般使用默认的优先级. 看到这里就知道为什么要填写 0, 0 吧)
    
    
    关于服务质量的说明:
    1. iOS 8.0 后
        告诉队列执行任务的"服务质量quality of service"
        QOS_CLASS_USER_INTERACTIVE 0x21,              用户交互(希望尽快完成,用户对结果很期望,不要放太耗时操作)       
        QOS_CLASS_USER_INITIATED 0x19,                用户期望(不要放太耗时操作)
        QOS_CLASS_DEFAULT 0x15,                       默认(不是给程序员使用的,用来重置对列使用的)
        QOS_CLASS_UTILITY 0x11,                       实用工具(耗时操作,可以使用这个选项)
        QOS_CLASS_BACKGROUND 0x09,                    后台
        QOS_CLASS_UNSPECIFIED 0x00,                   未指定(现在好像已经移除)
    
    2. iOS 7.0 之前 优先级
        DISPATCH_QUEUE_PRIORITY_HIGH 2                高优先级
        DISPATCH_QUEUE_PRIORITY_DEFAULT 0             默认优先级
        DISPATCH_QUEUE_PRIORITY_LOW (-2)              低优先级
        DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN  后台优先级
    
            提示:不要选择 BACKGROUND 的选项,苹果认为:BACKGROUND 表示用户不需要知道任务什么时候完成!
                       选择这个选项,速度慢的令人发指!不利于调试!
                      关于优先级,不要搞太负责,就用最简单的
    
           结论:如果要做 iOS 8.0 & iOS 7.0 的适配: dispatch_get_global_queue(0, 0);
             
          提示:如果在今后,iOS 8.0 & 9.0 适配的时候,应该选择 QOS_CLASS_UTILITY
        
          此内容只作为参考,具体查看 api 文档说明!
    */ 
    dispatch_queue_t
    dispatch_get_global_queue(long identifier, unsigned long flags);
    

    自己创建队列:

    // 创建一个队列
    // 此队列为串行队列
     dispatch_queue_t q = dispatch_queue_create("demo", NULL);
    
    
    // 函数的声明
    /*
     参数说明:
                * const char *label: 队列的名称
                * dispatch_queue_attr_t attr  : 队列的类型
                    - DISPATCH_QUEUE_SERIAL(NULL)   串行队列
                    - DISPATCH_QUEUE_CONCURRENT     并发队列
    */
    dispatch_queue_t
    dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
    

    队列的区分:

    • 主队列:在 app 启动的时候就创建的。我们只需要获取。默认情况下主队列只有1条线程,叫主线程。
    • 全局队列:apple 提供的一个并发队列。根据不同的优先级(服务质量)可以获取 5 种不同的全局队列
    • 自己创建的队列:这个队列是自己通过函数创建的。
      (我们可以操作的队列大约有 6 个。)

    2、队列选择的问题:

    我们使用 GCD 的目的只有一个:开辟新的线程执行耗时操作。
    一般情况下我不需要创建队列,只需要使用apple 为我们给提供的全局队列。进行异步执行任务就可以了。
    只有在第三方框架或者大型商业软件才使用自己创建的队列执行异步操作。

    • 并发队列 (全局队列)

      • 调度任务的时候,多个线程,而且是顺序不固定
      • 并发能力好
      • 效率高,速度快,费电,费钱
    • 串行队列

      • 调度任务,只有一条线程,顺序执行每一个任务
      • 并发能力差
      • 效率差,速度慢,省电,省钱
      • 用户并不是什么时候都会希望快的!

    选择依据:

    • 如果是WIFI 情况,使用全局队列,开启线程的数量6 条左右
    • 如果是3G/4G,使用串行队列,如果使用全局队列,开启线程的数量控制在2~3 条

    1、全局队列异步执行

    (全局队列默认是一个并发队列)
    异步会开线程
    objc 版本

    // 1、获取全局队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
        
    NSLog(@"before: %@", [NSThread currentThread]);
    // 2、创建任务
    void(^task)() = ^() {
        NSLog(@"task : %@", [NSThread currentThread]);  
    };
    // 3、将任务添加进异步队列
    dispatch_async(q, task);
    
    NSLog(@"after: %@", [NSThread currentThread]);
    
    // 打印结果
    2016-06-21 10:56:37.681 Thread-Objc[43762:2374000] before: <NSThread: 0x7fa5ebf02d10>{number = 1, name = main}
    2016-06-21 10:56:37.681 Thread-Objc[43762:2374000] after: <NSThread: 0x7fa5ebf02d10>{number = 1, name = main}
    2016-06-21 10:56:37.681 Thread-Objc[43762:2374217] task : <NSThread: 0x7fa5ebe01410>{number = 2, name = (null)}
    

    2、全局队列同步执行

    (全局队列默认是一个并发队列)
    同步执行不会开线程
    objc 版本

    // 1、获取全局队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);    
    
    NSLog(@"before: %@", [NSThread currentThread]);
    // 2、创建任务
    void(^task)() = ^() {
        NSLog(@"task : %@", [NSThread currentThread]);
    };    
    // 3、将任务添加进异步队列
     dispatch_sync(q, task);
    
    NSLog(@"after: %@", [NSThread currentThread]);
    
    // 打印结果
    2016-06-21 10:53:47.219 Thread-Objc[43695:2369064] before: <NSThread: 0x7fcdb1f04420>{number = 1, name = main}
    2016-06-21 10:53:47.220 Thread-Objc[43695:2369064] task : <NSThread: 0x7fcdb1f04420>{number = 1, name = main}
    2016-06-21 10:53:47.220 Thread-Objc[43695:2369064] after: <NSThread: 0x7fcdb1f04420>{number = 1, name = main}
    
    

    结论:

    • 同步:不会开启线程,就在当前线程执行
    • 异步:会开启线程,在其他线程执行block
      (不适用于主队列)

    多线程体验代码

    for (int index = 0; index < 10; index ++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"task : %@", [NSThread currentThread]);
        });
    }
    
    // 打印结果
    2016-06-21 11:03:57.780 Thread-Objc[43896:2382918] task : <NSThread: 0x7ff9b0c06070>{number = 5, name = (null)}
    2016-06-21 11:03:57.780 Thread-Objc[43896:2382919] task : <NSThread: 0x7ff9b0f09e00>{number = 3, name = (null)}
    2016-06-21 11:03:57.780 Thread-Objc[43896:2382911] task : <NSThread: 0x7ff9b0c0b920>{number = 2, name = (null)}
    2016-06-21 11:03:57.780 Thread-Objc[43896:2382929] task : <NSThread: 0x7ff9b0e00660>{number = 4, name = (null)}
    2016-06-21 11:03:57.781 Thread-Objc[43896:2382930] task : <NSThread: 0x7ff9b0d96680>{number = 6, name = (null)}
    2016-06-21 11:03:57.781 Thread-Objc[43896:2382918] task : <NSThread: 0x7ff9b0c06070>{number = 5, name = (null)}
    2016-06-21 11:03:57.781 Thread-Objc[43896:2382919] task : <NSThread: 0x7ff9b0f09e00>{number = 3, name = (null)}
    2016-06-21 11:03:57.781 Thread-Objc[43896:2382911] task : <NSThread: 0x7ff9b0c0b920>{number = 2, name = (null)}
    2016-06-21 11:03:57.781 Thread-Objc[43896:2382929] task : <NSThread: 0x7ff9b0e00660>{number = 4, name = (null)}
    2016-06-21 11:03:57.781 Thread-Objc[43896:2382930] task : <NSThread: 0x7ff9b0d96680>{number = 6, name = (null)}
    
    // 能够体现出gcd 底层是有线程池的 (number 体现出线程出现了复用)
    // 一个任务执行完毕后,线程可以被复用!
    // 如果用 NSThread,需要自己维护线程池,以及管理所有线程的生命周期!
    // 在实际开发中,新建线程是一个非常频繁的工作!(GCD 不需要程序员管理线程的创建工作)
    

    3、GCD 中多线程最简单的应用代码 ( 线程间通讯)

    //  全局队列 - 负责调度任务的
    //  异步执行任务
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        // 耗时操作的任务
        NSLog(@"task : %@", [NSThread currentThread]);
        
        
        // 通知主线程刷新界面
        /*
            这里使用  dispatch_async()  和  dispatch_sync() 效果是不一样的。
            dispatch_sync() 会等待当前线程执行完毕后才会执行 block 中的内容
            dispatch_async()  不会等待,会先答应 over 后再执行 block 中的内容。
        */ 
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"UI - task : %@", [NSThread currentThread]);
        });
        
        NSLog(@"over : %@", [NSThread currentThread]);
    });
    

    队列同步异步,串行并行之间的关系

    1、 队列和任务之间的关系(串行并行同步异步的组合)

    • 串行队列异步执行
        NSLog(@"before: %@", [NSThread currentThread]);
      // 创建一个队列(串行)
    dispatch_queue_t q = dispatch_queue_create("demo1", NULL);
    
      // 5 次异步
    for (int index = 0; index < 5; index ++) {
        
        // 将任务添加到队列
        dispatch_async(q, ^{
            NSLog(@"task : %@", [NSThread currentThread]);
        });
      }
    
      NSLog(@"after: %@", [NSThread currentThread]);
    
      // 打印结果
      2016-06-21 11:33:10.502 Thread-Objc[44380:2420806] before: <NSThread: 0x7fdf0be02f80>{number = 1, name = main}
      2016-06-21 11:33:10.502 Thread-Objc[44380:2420806] after: <NSThread: 0x7fdf0be02f80>{number = 1, name = main}
      2016-06-21 11:33:10.502 Thread-Objc[44380:2420806] <OS_dispatch_queue: demo1[0x7fdf0bd0dbb0]>
    2016-06-21 11:33:10.502 Thread-Objc[44380:2421052] task : <NSThread: 0x7fdf0be25210>{number = 2, name = (null)}
    2016-06-21 11:33:10.503 Thread-Objc[44380:2421052] task : <NSThread: 0x7fdf0be25210>{number = 2, name = (null)}
    2016-06-21 11:33:10.503 Thread-Objc[44380:2421052] task : <NSThread: 0x7fdf0be25210>{number = 2, name = (null)}
    2016-06-21 11:33:10.503 Thread-Objc[44380:2421052] task : <NSThread: 0x7fdf0be25210>{number = 2, name = (null)}
    2016-06-21 11:33:10.503 Thread-Objc[44380:2421052] task : <NSThread: 0x7fdf0be25210>{number = 2, name = (null)}
    
    异步执行会开线程,串行开1 条, NSLog(@"after: %@", [NSThread currentThread])执行的位置是不确定的。(将异步次数你就可以看到效果)
    
    • 串行队列同步执行
    NSLog(@"before: %@", [NSThread currentThread]);
    // 创建一个队列(串行)
    dispatch_queue_t q = dispatch_queue_create("demo1", NULL);
    
    // 5 次异步
    for (int index = 0; index < 5; index ++) {
        
        // 将任务添加到队列
        dispatch_sync(q, ^{
            NSLog(@"task : %@", [NSThread currentThread]);
        });
    }
    
    NSLog(@"after: %@", [NSThread currentThread]);
    
    // 打印结果
    2016-06-21 11:36:04.743 Thread-Objc[44420:2424798] before: <NSThread: 0x7ffd70d06750>{number = 1, name = main}
    2016-06-21 11:36:04.744 Thread-Objc[44420:2424798] task : <NSThread: 0x7ffd70d06750>{number = 1, name = main}
    2016-06-21 11:36:04.744 Thread-Objc[44420:2424798] task : <NSThread: 0x7ffd70d06750>{number = 1, name = main}
    2016-06-21 11:36:04.744 Thread-Objc[44420:2424798] task : <NSThread: 0x7ffd70d06750>{number = 1, name = main}
    2016-06-21 11:36:04.744 Thread-Objc[44420:2424798] task : <NSThread: 0x7ffd70d06750>{number = 1, name = main}
    2016-06-21 11:36:04.744 Thread-Objc[44420:2424798] task : <NSThread: 0x7ffd70d06750>{number = 1, name = main}
    2016-06-21 11:36:04.744 Thread-Objc[44420:2424798] after: <NSThread: 0x7ffd70d06750>{number = 1, name = main}
    
    同步执行不会开线程,只会在主线中运行,NSLog(@"after: %@", [NSThread currentThread])执行的位置是不确定的。(将异步次数你就可以看到效果)
    
    • 并行队列异步执行
    NSLog(@"before: %@", [NSThread currentThread]);
    // 创建一个队列(串行)
    dispatch_queue_t q = dispatch_queue_create("demo1", DISPATCH_QUEUE_CONCURRENT);
    
    // 5 次异步
    for (int index = 0; index < 5; index ++) {
        
        // 将任务添加到队列
        dispatch_async(q, ^{
            NSLog(@"task : %@", [NSThread currentThread]);
        });
    }
    
    NSLog(@"after: %@", [NSThread currentThread]);
    
    // 打印结果
    2016-06-21 11:49:06.187 Thread-Objc[44535:2441876] before: <NSThread: 0x7fd181400d20>{number = 1, name = main}
    2016-06-21 11:49:06.189 Thread-Objc[44535:2441876] after: <NSThread: 0x7fd181400d20>{number = 1, name = main}
    2016-06-21 11:49:06.190 Thread-Objc[44535:2442143] task : <NSThread: 0x7fd18147f7e0>{number = 2, name = (null)}
    2016-06-21 11:49:06.191 Thread-Objc[44535:2442152] task : <NSThread: 0x7fd181521180>{number = 3, name = (null)}
    2016-06-21 11:49:06.191 Thread-Objc[44535:2442159] task : <NSThread: 0x7fd18164a590>{number = 4, name = (null)}
    2016-06-21 11:49:06.191 Thread-Objc[44535:2442171] task : <NSThread: 0x7fd181415cf0>{number = 5, name = (null)}
    2016-06-21 11:49:06.191 Thread-Objc[44535:2442143] task : <NSThread: 0x7fd18147f7e0>{number = 2, name = (null)}
    
    异步执行会开起线程,开启的线程数是不确定的,NSLog(@"after: %@", [NSThread currentThread])执行的位置是不确定的。(将异步次数你就可以看到效果)
    
    • 并行队列同步执行
    NSLog(@"before: %@", [NSThread currentThread]);
    // 创建一个队列(串行)
    dispatch_queue_t q = dispatch_queue_create("demo1", DISPATCH_QUEUE_CONCURRENT);
    
    // 5 次异步
    for (int index = 0; index < 5; index ++) {
        
        // 将任务添加到队列
        dispatch_sync(q, ^{
            NSLog(@"task : %@", [NSThread currentThread]);
        });
    }
    
    NSLog(@"after: %@", [NSThread currentThread]);
    
    // 打印结果
    2016-06-21 11:53:47.185 Thread-Objc[44570:2447335] before: <NSThread: 0x7fcde34084b0>{number = 1, name = main}
    2016-06-21 11:53:47.187 Thread-Objc[44570:2447335] task : <NSThread: 0x7fcde34084b0>{number = 1, name = main}
    2016-06-21 11:53:47.187 Thread-Objc[44570:2447335] task : <NSThread: 0x7fcde34084b0>{number = 1, name = main}
    2016-06-21 11:53:47.187 Thread-Objc[44570:2447335] task : <NSThread: 0x7fcde34084b0>{number = 1, name = main}
    2016-06-21 11:53:47.188 Thread-Objc[44570:2447335] task : <NSThread: 0x7fcde34084b0>{number = 1, name = main}
    2016-06-21 11:53:47.188 Thread-Objc[44570:2447335] task : <NSThread: 0x7fcde34084b0>{number = 1, name = main}
    2016-06-21 11:53:47.188 Thread-Objc[44570:2447335] after: <NSThread: 0x7fcde34084b0>{number = 1, name = main}
    
    通过不会开线程,默认会在主线运行,NSLog(@"after: %@", [NSThread currentThread])执行的位置是不确定的。(将异步次数你就可以看到效果)
    

    总结:
    同步不开异步开,串行开一条,并发开多条。
    (适用于自己创建的队列)

    2、主队列同步和异步执行

    主队列默认就是一个串行队列
    主队列,专门负责在主线程上调度任务,所有的任务执行就应该在主线程上执行!
    (主队列不具有开启线程的能力 无论异步还是同步)

    • 主队列异步
    NSLog(@"before: %@", [NSThread currentThread]);
    // 主队列 - 程序一启动,主线程就已经存在,主队列也同时 就存在了,只需要获取不需要创建
    dispatch_queue_t q = dispatch_get_main_queue();
    
    // 5 次异步
    for (int index = 0; index < 5; index ++) {
        
        // 将任务添加到队列
        dispatch_async(q, ^{
            NSLog(@"task : %@", [NSThread currentThread]);
        });
    }
    
    // 进行线程阻塞操作
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"after: %@", [NSThread currentThread]);
    
    // 打印结果
    2016-06-21 12:00:42.136 Thread-Objc[44680:2455130] before: <NSThread: 0x7fb4f1c053e0>{number = 1, name = main}
    2016-06-21 12:00:42.137 Thread-Objc[44680:2455130] after: <NSThread: 0x7fb4f1c053e0>{number = 1, name = main}
    2016-06-21 12:00:42.148 Thread-Objc[44680:2455130] task : <NSThread: 0x7fb4f1c053e0>{number = 1, name = main}
    2016-06-21 12:00:42.150 Thread-Objc[44680:2455130] task : <NSThread: 0x7fb4f1c053e0>{number = 1, name = main}
    2016-06-21 12:00:42.150 Thread-Objc[44680:2455130] task : <NSThread: 0x7fb4f1c053e0>{number = 1, name = main}
    2016-06-21 12:00:42.151 Thread-Objc[44680:2455130] task : <NSThread: 0x7fb4f1c053e0>{number = 1, name = main}
    2016-06-21 12:00:42.151 Thread-Objc[44680:2455130] task : <NSThread: 0x7fb4f1c053e0>{number = 1, name = main}
    
    主队列异步是不会开启线程的,NSLog(@"after: %@", [NSThread currentThread])执行的位置是不确定的。(将异步次数你就可以看到效果)
    
    • 主队列同步 (会造成死锁)
    NSLog(@"before: %@", [NSThread currentThread]);
    // 创建一个队列(串行)
    dispatch_queue_t q = dispatch_get_main_queue();
    
    // 5 次异步
    for (int index = 0; index < 5; index ++) {
        
        // 将任务添加到队列
        dispatch_sync(q, ^{
            NSLog(@"task : %@", [NSThread currentThread]);
        });
    }
    
    NSLog(@"after: %@", [NSThread currentThread]);
    
    打印结果:
    2016-06-21 12:07:22.921 Thread-Objc[44793:2467944] before: <NSThread: 0x7fb84a500ac0>{number = 1, name = main}
    
    测试的结果是 : 死锁
    关于死锁:记住主队列的特点就容易理解!主线程有任务就暂时不调度任务!
    

    关于主队列不死锁的问题
    ( 就是我们常用的项目代码,只是将主线程异步改为同步)

    使用的方法是 block 的嵌套调用
    // 创建并发队列
    dispatch_queue_t q = dispatch_queue_create("demo", DISPATCH_QUEUE_CONCURRENT);
    
    
    // 使用 block 对任务进行嵌套
    void(^task)() = ^() {
     
        NSLog(@"Befor: %@",[NSThread currentThread]);
        
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"Main Task: %@",[NSThread currentThread]);
        });
        
        NSLog(@"After: %@",[NSThread currentThread]);
    };
    
    // 将任务添加到异步队列
    dispatch_async(q, task);
    

    相关文章

      网友评论

          本文标题:3、GCD-Objc - Part 1

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