0.4 GCD

2016-01-24  本文已影响70人  李师道

<导语>

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

说明:

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

<GCD 核心概念>

  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. 队列的选择

  1. 实际开发中,线程数量如何决定?
  1. 线程、队列与任务的关系

<同步&异步的写法>

一. 概念

二. 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<UITouch *> *)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

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

/**
 *  触摸结束事件
 */
- (void)touchesEnded:(NSSet<UITouch *> *)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

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

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

一. 特点

二. 队列创建

dispatch_queue_t queue = dispatch_queue_create("com.lishidao@163.queue", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue = dispatch_queue_create("com.lishidao@163.queue", NULL);

三. 串行队列演练

1. 串行队列, 同步执行

/**
 *  触摸开始事件:展示 串行队列的同步 执行结果
 *  1. 创建一个同步方法,在方法中
 *      > 创建一个串行队列
 *      > 创建三个任务,每个任务都输出当前任务名及执行的线程
 *      > 通过同步方法,把三个任务添加到队列中,队列会自动调用任务
 *  2. 在触摸开始事件中调用同步方法
 *  3. 输出结果如下
 *      > task1===<NSThread: 0x7ff668405c90>{number = 1, name = main}
 *      > task2===<NSThread: 0x7ff668405c90>{number = 1, name = main}
 *      > task3===<NSThread: 0x7ff668405c90>{number = 1, name = main}
 */
- (void)touchesBegan:(NSSet<UITouch *> *)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("com.lishidao@163.queue", 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. 输出结果如下
 *      > task1===<NSThread: 0x7f974be12ff0>{number = 4, name = (null)}
 *      > task2===<NSThread: 0x7f974be12ff0>{number = 4, name = (null)}
 *      > task3===<NSThread: 0x7f974be12ff0>{number = 4, name = (null)}
 */
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

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

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

    // 1.创建串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("com.lishidao@163.queue", 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("com.lishidao@163.queue", DISPATCH_QUEUE_CONCURRENT);

三. 并发队列演练

1. 并发队列,同步执行

/**
 *  触摸开始事件:展示 并发队列的同步 执行结果
 *  1. 创建一个同步方法,在方法中
 *      > 创建一个并发队列
 *      > 创建三个任务,每个任务都输出当前任务名及执行的线程
 *      > 通过同步方法,把三个任务添加到队列中,队列会自动调用任务
 *  2. 在触摸开始事件中调用同步方法
 *  3. 输出结果如下
 *      > task1===<NSThread: 0x7fc01be048a0>{number = 1, name = main}
 *      > task2===<NSThread: 0x7fc01be048a0>{number = 1, name = main}
 *      > task3===<NSThread: 0x7fc01be048a0>{number = 1, name = main}
 */
- (void)touchesBegan:(NSSet<UITouch *> *)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("com.lishidao@163.queue", 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. 并发队列,异步执行

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

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

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

    // 1.创建并发队列
    dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.lishidao@163.queue", 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);
}

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

一. 概念

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

三. 代码演示与总结

1. 全局队列 异步任务

/**
 *  触摸开始事件:展示 全局队列的同步 执行结果
 *  1. 创建一个同步方法,在方法中
 *      > 获取全局队列
 *      > 创建三个任务,每个任务都输出当前任务名及执行的线程
 *      > 通过同步方法,把三个任务添加到队列中,队列会自动调用任务
 *  2. 在触摸开始事件中调用同步方法
 *  3. 输出结果如下
 *      > task1===<NSThread: 0x7f9ac0c02ef0>{number = 1, name = main}
 *      > task2===<NSThread: 0x7f9ac0c02ef0>{number = 1, name = main}
 *      > task3===<NSThread: 0x7f9ac0c02ef0>{number = 1, name = main}
 */
- (void)touchesBegan:(NSSet<UITouch *> *)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. 并发队列,异步方法

/**
 *  触摸结束事件:展示 全局队列的异步 执行结果
 *  1. 创建一个异步方法,在方法中
 *      > 获取全局队列
 *      > 创建三个任务,每个任务都输出当前任务名及执行的线程
 *      > 通过异步方法,把三个任务添加到队列中,队列会自动调用任务
 *  2. 在触摸结束事件中调用异步方法
 *  3. 输出结果如下
 *      > task2===<NSThread: 0x7f9ac0c1fc90>{number = 3, name = (null)}
 *      > task1===<NSThread: 0x7f9ac0c1a4b0>{number = 2, name = (null)}
 *      > task3===<NSThread: 0x7f9ac0c08870>{number = 4, name = (null)}
 */
- (void)touchesEnded:(NSSet<UITouch *> *)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<UITouch *> *)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===<NSThread: 0x7f9af0c021b0>{number = 1, name = main}
 *      > task2===<NSThread: 0x7f9af0c021b0>{number = 1, name = main}
 *      > task3===<NSThread: 0x7f9af0c021b0>{number = 1, name = main}
 */
- (void)touchesEnded:(NSSet<UITouch *> *)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---");
}

<GCD 常用代码>

一. 异步执行任务

- (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 最常用代码组合!

- (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<UITouch *> *)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("com.lishidao@163.queue", 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("com.lishidao@163.queue", 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("com.lishidao@163.queue", DISPATCH_QUEUE_CONCURRENT);

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

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

代码示例

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

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

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

/**
 *  触摸事件
 */
- (void)touchesBegan:(NSSet<UITouch *> *)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");
}

代码示例

#pragma mark - 解析延时操作
/**
 *  1.封装执行延时操作的方法
 *  2.在viewDidLoad方法中调用方法
 *  3.输出结果,打印顺序
        > ----end
        > 下载完毕---<NSThread: 0x7fc2e9f030b0>{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");
}
- (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");
}

二. 代码示例

#pragma mark - 演示调度组
/**
 *  1.封装实现调度的组的方法
 *  2.在触摸开始方法中调用
 */

/**
 *  触摸开始时执行调度组
 */
- (void)touchesBegan:(NSSet<UITouch *> *)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去播放");
    });

}
上一篇 下一篇

猜你喜欢

热点阅读