0.4 GCD

<导语>

GCD是C语言的,C语言里面创建一个东西出来通过函数实现多线程技术,GCD里面所的函数都是dispatch(调度) 开头

说明:

本文中所有比较完整的代码的Demo,可以到github中下载,链接github

  1. 任务添加到队列,并且指定执行任务的函数
  2. 任务使用 block 封装
    • 任务的 block 没有参数也没有返回值
  3. 执行任务的函数
    • 异步 dispatch_async
      • 不用等待当前语句执行完毕,就可以执行下一条语句
      • 会开启线程执行 block 的任务
      • 异步是多线程的代名词
    • 同步 dispatch_sync
      • 必须等待当前语句执行完毕,才会执行下一条语句
      • 不会开启线程
      • 在当前执行 block 的任务
  4. 队列 - 负责调度任务
    • 第一类:需要我们自己创建,
    • 1> 串行队列
      • 一次只能"调度"一个任务
      • dispatch_queue_create("itheima", NULL);
    • 2> 并发队列
      • 一次可以"调度"多个任务
      • dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);
    • 第二类:由系统提供
    • 1> 全局队列
      • 就等于并发队列
      • 并发队列,需要我们程序员自己去创建
      • 全局队列是系统提供
    • 2> 主队列
      • 是一个奇葩
      • 主要用于回到主线程,所有代码在主线程执行
      • 专门用来在主线程上调度任务的队列
      • 不会开启线程
      • 主线程空闲时才会调度队列中的任务在主线程执行
      • dispatch_get_main_queue();

<阶段性小结>

  1. 开不开线程由执行任务的函数决定

    • 异步开,异步是多线程的代名词
    • 同步不开
  2. 开几条线程由队列决定

    • 串行队列开一条线程
    • 并发队列开多条线程,具体能开的线程数量由底层线程池决定
      • iOS 8.0 之后,GCD 能够开启非常多的线程
      • iOS 7.0 以及之前,GCD 通常只会开启 5~6 条线程
  3. 队列的选择

  • 多线程的目的:将耗时的操作放在后台执行!
  • 串行队列,只开一条线程,所有任务顺序执行
    • 如果任务有先后执行顺序的要求
    • 效率低 -> 执行慢 -> "省电"
    • 有的时候,用户其实不希望太快!例如使用 3G 流量,"省钱"
  • 并发队列,会开启多条线程,所有任务不按照顺序执行
    • 如果任务没有先后执行顺序的要求
    • 效率高 -> 执行快 -> "费电"
    • WIFI,包月
  1. 实际开发中,线程数量如何决定?
  • WIFI 线程数 6
  • 3G / 4G 移动开发的时候,2~3条,再多会费电费钱!
  1. 线程、队列与任务的关系
  • 任务:必须要有一个依靠,这个依靠就是线程,虽然我们的任务是由我们的队列去调度,但是执行必须在线程上
  • 线程:它就是App(进程)的最小执行单元
  • 队列与任务:.任务会加入到队列中,然后由队列去调度,然后去执行
  • 线程和任务:任务必须执行在一个线程上

<同步&异步的写法>

一. 概念

  • 同步
    • 在当前线程中执行,必须等待当前语句执行完毕,才会执行下一条语句
  • 异步
    • 不在当前线程中执行,不用等待当前语句执行完毕,就可以执行下一条语句

二. NSThread 中的同步 & 异步

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"start");

    // 同步执行
//    [self demo];
    // 异步执行
    [self performSelectorInBackground:@selector(demo) withObject:nil];

    NSLog(@"over");
}

- (void)demo {

    NSLog(@"%@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"demo 完成");
}

三. GCD中的同步&异步

#pragma mark - 同步和异步的写法示例
/**
 *  1. 在触摸开始事件中展示同步
 *
 *  2. 在触摸结束事件中展示异步
 */

/**
 *  触摸开始事件
 */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    // 1.GCD同步方法

    /**
     参数1:队列
     参数2:任务
     */
    NSLog(@"touch-begin-begin");

    dispatch_sync(dispatch_get_global_queue(0, 0), ^{

        NSLog(@"---gcd ---sync,%@", [NSThread currentThread]);
    });

    NSLog(@"touch-begin-end");

    /**
     同步的打印顺序
     打印 touch-begin-begin
     打印 ---gcd ---sync
     打印 touch-begin-end

     同步任务执行的线程:主线程
     {number = 1, name = main}
     */
}

/**
 *  触摸结束事件
 */
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    // 2.GCD异步方法
    /**
     参数1:队列
     参数2:任务

     异步:不会在`当前线程`(主线程)执行

     既然是异步去执行,那么就必须先去开线程,但是开线程会花费时间
     */

    NSLog(@"touch-end-begin");

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        NSLog(@"---gcd ---async, %@",[NSThread currentThread]);
    });

    NSLog(@"touch-end-end");

    /**
     异步的打印顺序
     打印 touch-end-begin
     打印 touch-end-end
     打印 ---gcd ---async

     异步任务执行的线程:子线程
     {number = 2, name = (null)}
     */
}

<串行队列的同步和异步>

一. 特点

  • 先进先出的方式,顺序调度队列中的任务执行
  • 无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务

二. 队列创建

dispatch_queue_t queue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue = dispatch_queue_create("[email protected]", NULL);

三. 串行队列演练

1. 串行队列, 同步执行

  • 打印顺序 : 从上到下,依次打印,因为串行的
  • 在哪条线程上执行:主线程,因为是同步方法,所以在当前线程里面执行,恰好当前线程是主线程,所以它就在主线程上面执行
  • 应用场景:开发中很少用
/**
 *  触摸开始事件:展示 串行队列的同步 执行结果
 *  1. 创建一个同步方法,在方法中
 *      > 创建一个串行队列
 *      > 创建三个任务,每个任务都输出当前任务名及执行的线程
 *      > 通过同步方法,把三个任务添加到队列中,队列会自动调用任务
 *  2. 在触摸开始事件中调用同步方法
 *  3. 输出结果如下
 *      > task1==={number = 1, name = main}
 *      > task2==={number = 1, name = main}
 *      > task3==={number = 1, name = main}
 */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    // 2.调用同步方法
    [self serialSync];
}

/**
 *  串行队列同步方法
 */
- (void)serialSync {

    // 1.创建一个串行队列
    //   > 参数1:队列的标识符号,一般是公司的域名倒写(const char *label)
    //   > 参数2:队列的类型(dispatch_queue_attr_t attr)
    //              DISPATCH_QUEUE_SERIAL     串行队列
    //              DISPATCH_QUEUE_CONCURRENT 并发队列

    dispatch_queue_t serialQueue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_SERIAL);

    // 2.创建三个任务
    void (^task1) () = ^(){

        NSLog(@"task1===%@", [NSThread currentThread]);
    };
    void (^task2) () = ^(){

        NSLog(@"task2===%@", [NSThread currentThread]);
    };
    void (^task3) () = ^(){

        NSLog(@"task3===%@", [NSThread currentThread]);
    };

    // 3.通过同步方法,把三个任务添加到队列中,队列会自动调用任务
    //     > 参数1:前面所创建的队列(dispatch_queue_t queue)
    //     > 参数2:block代码块,即创建的三个任务(^{
    //code
//})
    dispatch_sync(serialQueue, task1);
    dispatch_sync(serialQueue, task2);
    dispatch_sync(serialQueue, task3);
}

2. 串行队列, 异步执行

  • 打印顺序:从上到下依次执行,它是串行队列
  • 在哪条线程上执行:在子线程,因为它是异步执行,异步,就是不在当前线程里面执行
  • 应用场景:耗时间,有顺序的任务---1.登录--->2.付费--->3.才能看
/**
 *  触摸结束事件:展示 串行队列的异步 执行结果
 *  1. 创建一个同步方法,在方法中
 *      > 创建一个串行队列
 *      > 创建三个任务,每个任务都输出当前任务名及执行的线程
 *      > 通过异步方法,把三个任务添加到队列中,队列会自动调用任务
 *  2. 在触摸结束事件中调用异步方法
 *  3. 输出结果如下
 *      > task1==={number = 4, name = (null)}
 *      > task2==={number = 4, name = (null)}
 *      > task3==={number = 4, name = (null)}
 */
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    // 2.调用异步方法
    [self serialAsync];
}

/**
 *  1. 创建串行队列的异步方法
 */
- (void)serialAsync {

    // 1.创建串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_SERIAL);

    // 2.创建任务
    void (^task1) () = ^() {
        NSLog(@"task1===%@", [NSThread currentThread]);
    };
    void (^task2) () = ^() {
        NSLog(@"task2===%@", [NSThread currentThread]);
    };
    void (^task3) () = ^() {
        NSLog(@"task3===%@", [NSThread currentThread]);
    };

    // 3.用异步方法把任务添加到队列中
    dispatch_async(serialQueue, task1);
    dispatch_async(serialQueue, task2);
    dispatch_async(serialQueue, task3);
}

<并发队列的同步和异步>

一. 特点

  • 先进先出的方式,并发调度队列中的任务执行
  • 如果当前调度的任务是同步执行的,会等待任务执行完成后,再调度后续的任务
  • 如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行

二. 队列创建

dispatch_queue_t queue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_CONCURRENT);

三. 并发队列演练

1. 并发队列,同步执行

  • 打印顺序: 依次执行,因为它是同步的
  • 在哪条线程上执行:主线程,因为它是同步方法,它就在当前线程里面执行,主线程,依次执行
    • 当它遇到同步的时候,并发队列,还是依次执行
    • 所以说,方法的优先级会比队列的优先级高
    • 只要是同步方法,都只会在当前线程里面执行,不会开子线程
  • 应用场景: 开发中几乎不用
/**
 *  触摸开始事件:展示 并发队列的同步 执行结果
 *  1. 创建一个同步方法,在方法中
 *      > 创建一个并发队列
 *      > 创建三个任务,每个任务都输出当前任务名及执行的线程
 *      > 通过同步方法,把三个任务添加到队列中,队列会自动调用任务
 *  2. 在触摸开始事件中调用同步方法
 *  3. 输出结果如下
 *      > task1==={number = 1, name = main}
 *      > task2==={number = 1, name = main}
 *      > task3==={number = 1, name = main}
 */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    // 2.调用同步方法
    [self conCurrentSync];
}

/**
 *  1. 创建并发队列同步方法
 */
- (void)conCurrentSync {

    // 1.创建一个并发队列
    //   > 参数1:队列的标识符号,一般是公司的域名倒写(const char *label)
    //   > 参数2:队列的类型(dispatch_queue_attr_t attr)
    //              DISPATCH_QUEUE_SERIAL     串行队列
    //              DISPATCH_QUEUE_CONCURRENT 并发队列

    dispatch_queue_t conCurrentQueue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_CONCURRENT);

    // 2.创建三个任务
    void (^task1) () = ^(){

        NSLog(@"task1===%@", [NSThread currentThread]);
    };
    void (^task2) () = ^(){

        NSLog(@"task2===%@", [NSThread currentThread]);
    };
    void (^task3) () = ^(){

        NSLog(@"task3===%@", [NSThread currentThread]);
    };

    // 3.通过同步方法,把三个任务添加到队列中,队列会自动调用任务
    //     > 参数1:前面所创建的队列(dispatch_queue_t queue)
    //     > 参数2:block代码块,即创建的三个任务(^{
    //code
    //})
    dispatch_sync(conCurrentQueue, task1);
    dispatch_sync(conCurrentQueue, task2);
    dispatch_sync(conCurrentQueue, task3);
}

2. 并发队列,异步执行

  • 打印顺序: 无序的
  • 在哪条线程上执行:在子线程上执行,第一个任务,都在它自己的线程上执行开N条,它是由底层可调度线程池来决定的,可调度线程池它是有一个重用机制
  • 应用场景: 下载电视剧某一集的时候,可以把我们的片头,片尾,中间内容 一起下;最后,拼接组合一下,就可以播放了;片头,中间内容,片尾
/**
 *  触摸结束事件:展示 并发队列的异步 执行结果
 *  1. 创建一个异步方法,在方法中
 *      > 创建一个并发队列
 *      > 创建三个任务,每个任务都输出当前任务名及执行的线程
 *      > 通过异步方法,把三个任务添加到队列中,队列会自动调用任务
 *  2. 在触摸结束事件中调用异步方法
 *  3. 输出结果如下
 *      > task2==={number = 3, name = (null)}
 *      > task1==={number = 2, name = (null)}
 *      > task3==={number = 4, name = (null)}
 */
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    // 2.调用异步方法
    [self conCurrentAsync];
}

/**
 *  1. 创建并发队列的异步方法
 */
- (void)conCurrentAsync {

    // 1.创建并发队列
    dispatch_queue_t conCurrentQueue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_CONCURRENT);

    // 2.创建任务
    void (^task1) () = ^() {
        NSLog(@"task1===%@", [NSThread currentThread]);
    };
    void (^task2) () = ^() {
        NSLog(@"task2===%@", [NSThread currentThread]);
    };
    void (^task3) () = ^() {
        NSLog(@"task3===%@", [NSThread currentThread]);
    };

    // 3.用异步方法把任务添加到队列中
    dispatch_async(conCurrentQueue, task1);
    dispatch_async(conCurrentQueue, task2);
    dispatch_async(conCurrentQueue, task3);
}

<全局队列的同步和异步>

一. 概念

  • 是系统为了方便程序员开发提供的,其工作表现与并发队列一致

二. 全局队列 & 并发队列的区别

  • 全局队列
    • 没有名称
    • 无论 MRC & ARC 都不需要考虑释放
    • 日常开发中,建议使用"全局队列"
  • 并发队列
    • 有名字,和 NSThreadname 属性作用类似
    • 如果在 MRC 开发时,需要使用 dispatch_release(q); 释放相应的对象
    • dispatch_barrier 必须使用自定义的并发队列
    • 开发第三方框架时,建议使用并发队列

三. 代码演示与总结

1. 全局队列 异步任务

  • 打印顺序: 依次执行,因为它是同步的
  • 在哪条线程上执行:主线程,因为它是同步方法,它就在当前线程里面执行,主线程,依次执行
    • 当它遇到同步的时候,并发队列,还是依次执行
    • 所以说,方法的优先级会比队列的优先级高
    • 只要是同步方法,都只会在当前线程里面执行,不会开子线程
  • 应用场景: 开发中几乎不用
/**
 *  触摸开始事件:展示 全局队列的同步 执行结果
 *  1. 创建一个同步方法,在方法中
 *      > 获取全局队列
 *      > 创建三个任务,每个任务都输出当前任务名及执行的线程
 *      > 通过同步方法,把三个任务添加到队列中,队列会自动调用任务
 *  2. 在触摸开始事件中调用同步方法
 *  3. 输出结果如下
 *      > task1==={number = 1, name = main}
 *      > task2==={number = 1, name = main}
 *      > task3==={number = 1, name = main}
 */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    // 2.调用同步方法
    [self globalSync];
}

/**
 *  全局队列同步方法
 */
- (void)globalSync {

    // 1.获取全局队列
    //   > 参数1: iOS7表示的优先级; iOS8表示服务质量; 为了保证兼容iOS7&iOS8一般传入0
    //   > 参数2: 未来使用,传入0

    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

    // 2.创建三个任务
    void (^task1) () = ^(){

        NSLog(@"task1===%@", [NSThread currentThread]);
    };
    void (^task2) () = ^(){

        NSLog(@"task2===%@", [NSThread currentThread]);
    };
    void (^task3) () = ^(){

        NSLog(@"task3===%@", [NSThread currentThread]);
    };

    // 3.通过同步方法,把三个任务添加到队列中,队列会自动调用任务
    //     > 参数1:前面所获取的队列(dispatch_queue_t queue)
    //     > 参数2:block代码块,即创建的三个任务(^{
    //code
    //})
    dispatch_sync(globalQueue, task1);
    dispatch_sync(globalQueue, task2);
    dispatch_sync(globalQueue, task3);
}

2. 并发队列,异步方法

  • 打印顺序: 无序的
  • 在哪条线程上执行: 在子线程上执行,第一个任务,都在它自己的线程上执行开N条,它是由底层可调度线程池来决定的,可调度线程池它是有一个重用机制
  • 应用场景: 下载电视剧某一集的时候,可以把我们的片头,片尾,中间内容 一起下;最后,拼接组合一下,就可以播放了;片头,中间内容,片尾
/**
 *  触摸结束事件:展示 全局队列的异步 执行结果
 *  1. 创建一个异步方法,在方法中
 *      > 获取全局队列
 *      > 创建三个任务,每个任务都输出当前任务名及执行的线程
 *      > 通过异步方法,把三个任务添加到队列中,队列会自动调用任务
 *  2. 在触摸结束事件中调用异步方法
 *  3. 输出结果如下
 *      > task2==={number = 3, name = (null)}
 *      > task1==={number = 2, name = (null)}
 *      > task3==={number = 4, name = (null)}
 */
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    // 2.调用异步方法
    [self globalAsync];
}

/**
 *  并发队列的异步方法
 */
- (void)globalAsync {

    // 1.获取全局队列
    //   > 参数1: iOS7表示的优先级; iOS8表示服务质量; 为了保证兼容iOS7&iOS8一般传入0
    //   > 参数2: 未来使用,传入0

    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

    // 2.创建任务
    void (^task1) () = ^() {
        NSLog(@"task1===%@", [NSThread currentThread]);
    };
    void (^task2) () = ^() {
        NSLog(@"task2===%@", [NSThread currentThread]);
    };
    void (^task3) () = ^() {
        NSLog(@"task3===%@", [NSThread currentThread]);
    };

    // 3.用异步方法把任务添加到队列中
    dispatch_async(globalQueue, task1);
    dispatch_async(globalQueue, task2);
    dispatch_async(globalQueue, task3);
}

运行效果与并发队列相同

3. 参数解析

  1. 服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级

    • iOS 8.0(新增,暂时不能用,今年年底)
      • 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, 未指定,可以和iOS 7.0 适配
    • iOS 7.0
      • DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
      • DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
      • DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
      • DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
  2. 为未来保留使用的,应该永远传入0

注意:如果要适配 iOS 7.0 & 8.0,使用以下代码:
dispatch_get_global_queue(0, 0);

<主队列的同步和异步>

一. 特点

  • 专门用来在主线程上调度任务的队列
  • 不会开启线程
  • 先进先出的方式,在主线程空闲时才会调度队列中的任务在主线程执行
  • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度

二. 队列获取

  • 主队列是负责在主线程调度任务的
  • 会随着程序启动一起创建
  • 主队列只需要获取不用创建
dispatch_queue_t queue = dispatch_get_main_queue();

三. 代码演示

1. 主队列,同步方法

  • 主队列,同步任务有问题,不能用
  • 虽然进入到了同步方法,但并没有继续执行
    • 原因:主队列只有在主线程空闲的时候,才会去调度它里面的任务去执行
    • 因为是同步方法,只能顺序执行,同步方法在等主队列任务的执行,而主队列任务在等同步方法的执行
    • 此处,程序进入了死等状态
/**
 *  触摸开始事件:展示 主队列的同步 执行结果
 *  1. 创建一个同步方法,在方法中
 *      > 添加输出,验证来到同步方法了
 *      > 获取主队列
 *      > 创建三个任务,每个任务都输出当前任务名及执行的线程
 *      > 通过同步方法,把三个任务添加到队列中,队列会自动调用任务
 *  2. 在触摸开始事件中调用同步方法
 *  3. 输出结果如下
 *      > [ViewController mainSync]
 */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    // 2.调用同步方法(如果调用,程序会进入死等)
    //[self mainSync];
}

/**
 *  主队列同步方法
 */
- (void)mainSync {
    NSLog(@"%s",__func__);

    // 1.获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    // 2.创建三个任务
    void (^task1) () = ^(){

        NSLog(@"task1===%@", [NSThread currentThread]);
    };
    void (^task2) () = ^(){

        NSLog(@"task2===%@", [NSThread currentThread]);
    };
    void (^task3) () = ^(){

        NSLog(@"task3===%@", [NSThread currentThread]);
    };

    // 3.通过同步方法,把三个任务添加到队列中,队列会自动调用任务
    //     > 参数1:前面所获取的队列(dispatch_queue_t queue)
    //     > 参数2:block代码块,即创建的三个任务(^{
    //code
    //})
    dispatch_sync(mainQueue, task1);
    dispatch_sync(mainQueue, task2);
    dispatch_sync(mainQueue, task3);
}

2. 主队列,异步方法

/**
 *  触摸结束事件:展示 主队列的异步 执行结果
 *  1. 创建一个异步方法,在方法中
 *      > 获取主队列
 *      > 创建三个任务,每个任务都输出当前任务名及执行的线程
 *      > 通过异步方法,把三个任务添加到队列中,队列会自动调用任务
 *      > 添加输出,验证主队列执行顺序
 *  2. 在触摸结束事件中调用异步方法
 *  3. 输出结果如下
 *      > task1==={number = 1, name = main}
 *      > task2==={number = 1, name = main}
 *      > task3==={number = 1, name = main}
 */
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    // 2.调用异步方法
    [self mainAsync];
}

/**
 *  主队列的异步方法
 */
- (void)mainAsync {

    // 1.获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    // 2.创建任务
    void (^task1) () = ^() {
        NSLog(@"task1===%@", [NSThread currentThread]);
    };
    void (^task2) () = ^() {
        NSLog(@"task2===%@", [NSThread currentThread]);
    };
    void (^task3) () = ^() {
        NSLog(@"task3===%@", [NSThread currentThread]);
    };

    // 3.用异步方法把任务添加到队列中
    dispatch_async(mainQueue, task1);
    dispatch_async(mainQueue, task2);
    dispatch_async(mainQueue, task3);

    // 4.验证任务执行顺序
    NSLog(@"---mainAsync---");
}

一. 异步执行任务

- (void)gcdDemo1 {
    // 1. 全局队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);

    // 2. 任务
    void (^task)() = ^ {
        NSLog(@"%@", [NSThread currentThread]);
    };

    // 3. 指定执行任务的函数
    // 异步执行任务 - 新建线程,在新线程执行 task
    dispatch_async(q, task);

    NSLog(@"come here");
}

注意:如果等待时间长一些,会发现线程的 number 发生变化,由此可以推断 gcd 底层线程池的工作

二. 同步执行任务

- (void)gcdDemo1 {
    // 1. 全局队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);

    // 2. 任务
    void (^task)() = ^ {
        NSLog(@"%@", [NSThread currentThread]);
    };

    // 3. 指定执行任务的函数
    // 同步执行任务 - 不开启线程,在当前线程执行 task
    dispatch_sync(q, task);

    NSLog(@"come here");
}

三. 精简代码

- (void)gcdDemo2 {
    for (int i = 0; i < 10; ++i) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"%@ %@", [NSThread currentThread], @"hello");
        });
    }
}

NSThread 的对比

  1. 所有的代码写在一起的,让代码更加简单,易于阅读和维护
    • NSThread 通过 @selector 指定要执行的方法,代码分散
    • GCD 通过 block 指定要执行的代码,代码集中
  2. 使用 GCD 不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期
  3. 如果要开多个线程 NSThread 必须实例化多个线程对象
  4. NSThreadNSObject 的分类方法实现的线程间通讯,GCDblock

四. 线程间通讯

- (void)gcdDemo3 {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"耗时操作 %@", [NSThread currentThread]);

        // 耗时操作之后,更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"更新 UI %@", [NSThread currentThread]);
        });
    });
}

以上代码是 GCD 最常用代码组合!

  • 如果要在更新 UI 之后,继续做些事情,可以使用以下代码
- (void)gcdDemo4 {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"耗时操作");

        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"更新UI");
        });

        NSLog(@"更新UI完毕");
    });
}

五. 网络下载图片

- (void)viewDidLoad {
    [super viewDidLoad];

    // 1.添加图片框
    [self setupImageView];

}

/**
 *  添加图片框
 */
- (void)setupImageView {

    UIImageView *imageView = [[UIImageView alloc] init];
    imageView.frame = CGRectMake(100, 200, 300, 200);
    [self.view addSubview:imageView];
    _imageView = imageView;
}

/**
 *  触摸事件
 */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    // 1.全局队列,异步方法
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        // 下载网络图片
        NSLog(@"%@==下载图片", [NSThread currentThread]);
        NSURL *url = [NSURL URLWithString:@"http://g.hiphotos.baidu.com/image/pic/item/f31fbe096b63f624cd2991e98344ebf81b4ca3e0.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        [NSThread sleepForTimeInterval:3];

        // 2.主队列,异步方法
        dispatch_async(dispatch_get_main_queue(), ^{

            // 更新UI
            NSLog(@"%@==更新UI", [NSThread currentThread]);
            self.imageView.image = image;
        });

    });
}

只有异步方法,才具备开启线程的能力

主队列是个奇葩--主队列使用的时候,千万不要使用同步方法,使用异步方法也不会开子线程,因为主队列只在主线程里面执行

<同步任务的作用>

同步任务,可以让其他异步执行的任务,依赖某一个同步任务

例如:在用户登录之后,再异步下载文件!

- (void)gcdDemo1 {
    dispatch_queue_t queue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(queue, ^{
        NSLog(@"登录 %@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"下载 A %@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"下载 B %@", [NSThread currentThread]);
    });
}
  • 代码改造,让登录也在异步执行
- (void)gcdDemo2 {
    // 并发队列
    dispatch_queue_t queue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_CONCURRENT);

    void (^task)() = ^{
        // 同步
        dispatch_sync(queue, ^{
            NSLog(@"登录 %@", [NSThread currentThread]);
        });
        // 异步
        dispatch_async(queue, ^{
            NSLog(@"下载 A %@", [NSThread currentThread]);
        });
        // 异步
        dispatch_async(queue, ^{
            NSLog(@"下载 B %@", [NSThread currentThread]);
        });
    };

    dispatch_async(queue, task);
}
  • 主队列调度同步队列不死锁
- (void)gcdDemo3 {
    // 并发队列
    dispatch_queue_t queue = dispatch_queue_create("[email protected]", DISPATCH_QUEUE_CONCURRENT);

    void (^task)() = ^ {
        // 同步
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"死?");
        });
    };
    // 异步
    dispatch_async(queue, task);
}

主队列在主线程空闲时才会调度队列中的任务在主线程执行

代码示例

同步的作用:为了保证我们任务执行的先后顺序

这个在耗时操作里面,又要保证任务的先后顺序的时候,用得最多

1.登录 2.同时下载三部大片

/**
 *  触摸事件
 */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    // 执行耗时操作
    [self execLongTimeOperation];
}

/**
 *  执行耗时操作的方法
 */
- (void)execLongTimeOperation {

    // 全局队列,异步方法:开启子线程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        // 1.当前队列,同步方法:登陆(保证先执行登陆)
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{

            NSLog(@"登陆===%@", [NSThread currentThread]);
            [NSThread sleepForTimeInterval:3];
        });

        // 2.同时下载三部电影(不需要先后顺序)
        dispatch_async(dispatch_get_global_queue(0, 0), ^{

            NSLog(@"downLoadA----%@",[NSThread currentThread]);
        });

        dispatch_async(dispatch_get_global_queue(0, 0), ^{

            NSLog(@"downLoadV----%@",[NSThread currentThread]);
        });

        dispatch_async(dispatch_get_global_queue(0, 0), ^{

            NSLog(@"downLoadI----%@",[NSThread currentThread]);
        });

    });
}

<延迟操作>

// MARK: - 延迟执行,实现原理
- (void)delay {
    /**
     从现在开始,经过多少纳秒,由"队列"调度异步执行 block 中的代码

     参数
     1. when    从现在开始,经过多少纳秒
     2. queue   队列
     3. block   异步执行的任务
     */
    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
    void (^task)() = ^ {
        NSLog(@"%@", [NSThread currentThread]);
    };
    // 主队列
//    dispatch_after(when, dispatch_get_main_queue(), task);
    // 全局队列
//    dispatch_after(when, dispatch_get_global_queue(0, 0), task);
    // 串行队列
    dispatch_after(when, dispatch_queue_create("lishidao", NULL), task);

    NSLog(@"come here");
}

- (void)after {
    [self.view performSelector:@selector(setBackgroundColor:) withObject:[UIColor orangeColor] afterDelay:1.0];

    NSLog(@"come here");
}

代码示例

  • 执行过程中,界面的segment是可以交互的,没有阻塞主线程
  • dispatch_after是异步的,先执行完后面的操作,又回来执行异步方法中操作
  • 应用场景: 动画
#pragma mark - 解析延时操作
/**
 *  1.封装执行延时操作的方法
 *  2.在viewDidLoad方法中调用方法
 *  3.输出结果,打印顺序
        > ----end
        > 下载完毕---{number = 1, name = main}
 */

- (void)viewDidLoad {
    [super viewDidLoad];

    // 执行延时操作
    [self execDispatchAfter];
}

/**
 *  执行延时操作的方法
 */
- (void)execDispatchAfter {

    /**
     参数1.延时多少纳秒,整个延迟3秒
     参数2:是决定,参数在哪个线程里面调用
     参数3:任务执行的代码块
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        NSLog(@"下载完毕---%@", [NSThread currentThread]);
    });
    NSLog(@"----end");
}

<一次性执行>

一. 经典场景

有的时候,在程序开发中,有些代码只想从程序启动就只执行一次,典型的应用场景就是“单例”

// MARK: 一次性执行
- (void)once {
    static dispatch_once_t onceToken;
    NSLog(@"%ld", onceToken);

    dispatch_once(&onceToken, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"一次性吗?");
    });
    NSLog(@"come here");
}
  • dispatch 内部也有一把锁,是能够保证"线程安全"的!而且是苹果公司推荐使用的
  • 以下代码用于测试多线程的一次性执行
- (void)demoOnce {
    for (int i = 0; i < 10; ++i) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self once];
        });
    }
}

二. 单例测试

1. 单例的特点

  1. 在内存中只有一个实例
  2. 提供一个全局的访问点

2. 单例实现

// 使用 dispatch_once 实现单例
+ (instancetype)sharedSingleton {
    static id instance;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });

    return instance;
}

面试时只要实现上面 sharedSingleton 方法即可

<调度组>

一. 常规用法

- (void)group1 {

    // 1. 调度组
    dispatch_group_t group = dispatch_group_create();

    // 2. 队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);

    // 3. 将任务添加到队列和调度组
    dispatch_group_async(group, q, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"任务 1 %@", [NSThread currentThread]);
    });
    dispatch_group_async(group, q, ^{
        NSLog(@"任务 2 %@", [NSThread currentThread]);
    });
    dispatch_group_async(group, q, ^{
        NSLog(@"任务 3 %@", [NSThread currentThread]);
    });

    // 4. 监听所有任务完成
    dispatch_group_notify(group, q, ^{
        NSLog(@"OVER %@", [NSThread currentThread]);
    });

    // 5. 判断异步
    NSLog(@"come here");
}

二. 代码示例

  • 调度组监听的实现原理:
  •  每一个任务加入调度组时计数器+1
    
  •  每完成一个任务调度组内计数器-1
    
  •  技术器为0时,在参数2的线程中执行参数的操作
    
#pragma mark - 演示调度组
/**
 *  1.封装实现调度的组的方法
 *  2.在触摸开始方法中调用
 */

/**
 *  触摸开始时执行调度组
 */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    // 执行调度组
    [self execGroupDispatch];
}

/**
 *  执行调度组的方法
 */
- (void)execGroupDispatch {

    // 1.创建调度组
    dispatch_group_t group = dispatch_group_create();

    // 2.获取全局队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

    // 3.三个下载任务
    void (^task1) () = ^(){
        NSLog(@"%@--下载片头", [NSThread currentThread]);
    };
    void (^task2) () = ^(){
        NSLog(@"%@--下载内容", [NSThread currentThread]);
    };
    void (^task3) () = ^(){
        NSLog(@"%@--下载片尾", [NSThread currentThread]);
    };

    // 4.将队列和任务添加到调度组
    dispatch_group_async(group, globalQueue, task1);
    dispatch_group_async(group, globalQueue, task2);
    dispatch_group_async(group, globalQueue, task3);

    // 5.监听函数
    /**
     参数1:组
     参数2:参数3在哪个线程里面执行
     参数3:组内完全下载完毕之后,需要执行的代码
     */
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //表示组内的所有任务都完成之后,会来到这里

        NSLog(@"把下好的视频按照顺序拼接好,然后显示在UI去播放");
    });

}

你可能感兴趣的:(0.4 GCD)