iOS开发多线程基础讲解三(GCD)

本文中所有代码演示均有GitHub源码,点击下载

本节重点是队列和任务的组合,不多说,先上图!!

01-同步任务执行方式.gif
02-异步任务执行方式.gif
03-串行队列调度同步和异步任务执行.gif
04-并发队列调度异步任务执行.gif

C 语言 /管理线程不方便!

摘要:GCD 核心概念:

  1. 任务添加到队列,并且指定执行任务的函数
  2. 任务使用 block 封装
    • 任务的 block 没有参数也没有返回值
  3. 执行任务的函数
    • 异步 dispatch_async
      • 不用等待当前语句执行完毕,就可以执行下一条语句
      • 会开启线程执行 block 的任务
      • 异步是多线程的代名词
    • 同步 dispatch_sync
      • 必须等待当前语句执行完毕,才会执行下一条语句
      • 不会开启线程
      • 在当前执行 block 的任务
  4. 队列 - 负责调度任务
    • 串行队列
      • 一次只能"调度"一个任务
      • dispatch_queue_create("wpf_silence", NULL);
    • 并发队列
      • 一次可以"调度"多个任务
      • dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);
    • 主队列
      • 专门用来在主线程上调度任务的队列
      • 不会开启线程
      • 主线程空闲时才会调度队列中的任务在主线程执行
      • dispatch_get_main_queue();

阶段性小结

  • 开不开线程由执行任务的函数决定
    • 异步开,异步是多线程的代名词
    • 同步不开
  • 开几条线程由队列决定
    • 串行队列开一条线程
    • 并发队列开多条线程,具体能开的线程数量由底层线程池决定
      • iOS 8.0 之后,GCD 能够开启非常多的线程
      • iOS 7.0 以及之前,GCD 通常只会开启 5~6 条线程
  • 组合总结:
    • 异步函数 + 串行队列:开启一条子线程,任务按顺序执行
    • 异步函数 + 并行队列:只有二者结合才有意义开启多条子线程,任务同时进行,线程可复用
    • 同步函数 + 串行队列:不开线程,同步执行,在当前线程执行
    • 同步函数 + 并行队列:不开线程,顺序执行

队列的选择

  • 多线程的目的:将耗时的操作放在后台执行!

  • 串行队列,只开一条线程,所有任务顺序执行

    • 如果任务有先后执行顺序的要求
    • 效率低 -> 执行慢 -> "省电"
    • 有的时候,用户其实不希望太快!例如使用 3G 流量,"省钱"
  • 并发队列,会开启多条线程,所有任务不按照顺序执行

    • 如果任务没有先后执行顺序的要求
    • 效率高 -> 执行快 -> "费电"
    • WIFI,包月

实际开发中,线程数量如何决定?

  • WIFI 线程数 6
  • 3G / 4G 移动开发的时候,2~3条,再多会费电费钱!

一. 同步 & 异步

概念

  • 同步
    • 在当前线程中执行,必须等待当前语句执行完毕,才会执行下一条语句,同步 从上到下顺序执行
  • 异步
    • 不在当前线程中执行,不用等待当前语句执行完毕,就可以执行下一条语句(但是开辟线程会浪费时间),异步多线程的代名词
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 常用代码

异步执行任务

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

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

    // 1. 全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

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

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

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

    /*
    输出结果: 说明是另外开辟一条子线程进行异步任务,而不是在当前线层中进行,但是开线程会浪费时间

      touch--->begin
      touch--->end
      async----><NSThread: 0x7fbfc8518190>{number = 2, name = (null)}
     */
}

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

同步执行任务

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

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

    // 1. 全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

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

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

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

    /*
      touch--->begin
      sync----><NSThread: 0x7f99c8c03650>{number = 1, name = main}
      touch--->end
     */
}

精简代码

- (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];

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%s %@", __FUNCTION__, [NSThread currentThread]);

        // 1. 异步下载网络图片
        NSURL *url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/1f178a82b9014a901bef674aaa773912b21bee70.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];

        // 2. 完成后更新 UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            [self.imageView sizeToFit];

            self.scrollView.contentSize = image.size;
        });
    });
}

三. 串行队列

特点

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

队列创建

dispatch_queue_t queue = dispatch_queue_create("com.apple.queue", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue = dispatch_queue_create("com.apple.queue", NULL);

串行队列演练

  • 触摸事件调用方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    NSLog(@"touchBegin!", [NSThread currentThread]);

    // 串行队列,同步方法
    //[self serailSync];


    // 串行队列,异步方法
    [self serailAsync];

    NSLog(@"touchEnd!--->%@", [NSThread currentThread]);
}
  • 串行队列 同步执行
/**
 提问:是否开线程?是否顺序执行?
 */
- (void)serailSync {
    // 1. 创建一个串行队列

    /*
     参数一:队列的标识符号,一般是公司名称倒写
     参数二:队列的类型
     */

    dispatch_queue_t serailQueue = dispatch_queue_create("com.apple", DISPATCH_QUEUE_SERIAL);


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

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

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

    // 3. 将创建好的三个任务添加到串行队列中,这个队列就开始调用我们的任务
    dispatch_sync(serailQueue, task1);
    dispatch_sync(serailQueue, task2);
    dispatch_sync(serailQueue, task3);
}


因为是串行队列,因此在当前线程(主线程)中从上到下顺序执行

     应用场景:很少使用


/*
     打印结果:

     touchBegin!---><NSThread: 0x7fbe72604d30>{number = 1, name = main}
     task1---><NSThread: 0x7fbe72604d30>{number = 1, name = main}
     task2---><NSThread: 0x7fbe72604d30>{number = 1, name = main}
     task3---><NSThread: 0x7fbe72604d30>{number = 1, name = main}
     touchEnd!---><NSThread: 0x7fbe72604d30>{number = 1, name = main}
*/
  • 串行队列 异步执行
/**
 提问:是否开线程?是否顺序执行?come here 的位置?
 */
- (void)serailAsync {

    // 1. 创建一个串行队列
    dispatch_queue_t serailQueue = dispatch_queue_create("com.apple", NULL);

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

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

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

    // 3. 将创建的任务添加到队列中
    dispatch_async(serailQueue, task1);
    dispatch_async(serailQueue, task2);
    dispatch_async(serailQueue, task3);

    /*
     因为是异步方法,所以新建子线程执行,这里只开辟一条子线程
     因为是串行,因此从上到下依次打印

     应用场景:
            耗时间,有顺序的场景

        1.登录--->2.付费--->3.才能看
     */

    /*
     打印结果:

    touchBegin!---><NSThread: 0x7fb191402480>{number = 1, name = main}
    touchEnd!---><NSThread: 0x7fb191402480>{number = 1, name = main}
    task1---><NSThread: 0x7fb19145f100>{number = 2, name = (null)}
    task2---><NSThread: 0x7fb19145f100>{number = 2, name = (null)}
    task3---><NSThread: 0x7fb19145f100>{number = 2, name = (null)}
     */
}

四. 并发队列

特点

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

队列创建

dispatch_queue_t queue = dispatch_queue_create("com.apple.queue", DISPATCH_QUEUE_CONCURRENT);

并发队列演练

  • 调用方法的点击事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    NSLog(@"bigin--->%@", [NSThread currentThread]);

    // 并发队列,同步执行
    [self conCurrentSync];

    // 并发队列,异步执行
    //[self conCurrentAsync];

    NSLog(@"end--->%@", [NSThread currentThread]);
}
  • 并发队列 同步执行
- (void)conCurrentSync {

    // 1. 队列
    /*
     参数一:const char *label              队列的标识符,一般是公司域名的倒写
     参数二:dispatch_queue_attr_t attr     队列的类型
     */
    dispatch_queue_t q = dispatch_queue_create("com.apple", DISPATCH_QUEUE_CONCURRENT);

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

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

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

    // 3. 将任务添加到队列中
    dispatch_sync(conCurrentQueue, task1);
    dispatch_sync(conCurrentQueue, task2);
    dispatch_sync(conCurrentQueue, task3);

    /*
    因为是同步执行,所以都在当前线程(主线程)执行,不会开辟新的线程

     ******  遇到同步的时候,并发队列,还是依次执行,因此方法的优先级比队列的优先级高  ******

     应用场景:开发中几乎不用
     */

     /*
     打印结果:

      bigin---><NSThread: 0x7fabaac01c20>{number = 1, name = main}
      task1---><NSThread: 0x7fabaac01c20>{number = 1, name = main}
      task2---><NSThread: 0x7fabaac01c20>{number = 1, name = main}
      task3---><NSThread: 0x7fabaac01c20>{number = 1, name = main}
      end---><NSThread: 0x7fabaac01c20>{number = 1, name = main}
     */
}
  • 并发队列 异步执行

- (void)conCurrentAsync {

    // 1. 队列
    dispatch_queue_t q = dispatch_queue_create("com.apple", DISPATCH_QUEUE_CONCURRENT);

    // 2. 创建三个block类型的任务
    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);

    /*
     无序

     因为是并发队列,所以任务都不在当前线程中执行,而是另外开辟子线程
     另外开辟子线程需要耗时间,因为是异步执行,因此end 可以跳过还未执行完毕的三个任务

     开N条子线程,是由底层可调度线程池来决定的,可调度线程池有一个可重用机制

     应用场景:
        当我们下载电影的时候,可以把片头、片尾、中间部分分开下载,等到都下载完毕后拼接一下就好了
     */

     /*
     打印结果:

      bigin---><NSThread: 0x7ff159e050c0>{number = 1, name = main}
      task1---><NSThread: 0x7ff159d01960>{number = 2, name = (null)}
      end---><NSThread: 0x7ff159e050c0>{number = 1, name = main}
      task3---><NSThread: 0x7ff159c03890>{number = 3, name = (null)}
      task2---><NSThread: 0x7ff159f1e4c0>{number = 4, name = (null)}
     */
}

五. 全局队列

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

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

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

全局队列 异步任务


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

    // 2. 执行任务
    for (int i = 0; i < 10; ++i) {
        dispatch_async(q, ^{
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }

    NSLog(@"come here");
}

运行效果与并发队列相同

参数

  1. ios7中表示队列对任务调度的优先级,ios8中表示服务质量

    • 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();

主队列演练

  • 主队列,异步执行
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self mainAsync];

    [NSThread sleepForTimeInterval:1];
    NSLog(@"over");
}

- (void)mainAsync {

    dispatch_queue_t queue = dispatch_get_main_queue();

    for (int i = 0; i < 10; ++i) {
        dispatch_async(queue, ^{
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
        NSLog(@"---> %d", i);
    }
}

/*
    使用场景:
        当做了耗时操作回到主线程更新UI的时候,非他不可
*/

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

  • 主队列,同步执行
// MARK: 主队列,同步任务

#warning 有问题,不能用,因为主队列只会在主线程有空闲的时候才会调度,然后里面的任务才会执行,造成死锁、死等,主线程卡死
- (void)mainSync {
    // 1. 队列
    dispatch_queue_t q = dispatch_get_main_queue();

    // 2. 同步
    dispatch_sync(q, ^{
        NSLog(@"%@", [NSThread currentThread]);
    });

}

主队列主线程相互等待会造成死锁,死循环,原因如下:

  • 如果在主线程中运用主队列同步,就是把任务放到了主线程的队列中
  • 同步对于任务是立刻执行的,那么当吧第一个任务放进主队列时,他就会立马执行
  • 可是主线程现在正在助理 syncMain 方法,任务需要等 syncMain 执行完才能执行
  • syncMain执行到第一个任务的时候,又要等第一个任务执行完才能往下执行第二个第三个任务
  • 这样syncMain 方法就和第一个第一个任务互相等待,形成了死锁

七. 同步任务的作用

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

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

/*
 同步的作用:保证任务执行的先后顺序

 先登陆,然后同时下载三部小视频
 */

- (void)execLongTimeOperation {

    // 这句代码有无均可
    dispatch_async(dispatch_get_global_queue(0, 0), ^{


        // 1. 首先登陆,同步执行,在当前线程中执行
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"login--->%@", [NSThread currentThread]);
        });

        // 2. 下载电影,并发执行
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"dloadA--->%@", [NSThread currentThread]);
        });

        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"dloadV--->%@", [NSThread currentThread]);
        });

        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"dloadI--->%@", [NSThread currentThread]);
        });

    });

    /*
     打印结果:

     login---><NSThread: 0x7fbd01e01c50>{number = 2, name = (null)}
     dloadA---><NSThread: 0x7fbd01e01c50>{number = 2, name = (null)}
     dloadV---><NSThread: 0x7fbd01d02580>{number = 3, name = (null)}
     dloadI---><NSThread: 0x7fbd01d13e50>{number = 4, name = (null)}
     */
}

  • 主队列调度同步队列不死锁
- (void)gcdDemo1 {

    dispatch_queue_t queue = dispatch_queue_create("com.apple.queue", DISPATCH_QUEUE_CONCURRENT);

    void (^task)() = ^ {
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"死?");
        });
    };

    dispatch_async(queue, task);
}

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

八. 延迟操作

// MARK: - 延迟执行
- (void)execDispatchAfter {

    NSLog(@"begin");

    /**
     从现在开始,经过多少纳秒,由"队列"调度异步执行 block 中的代码

     参数
     1. when    从现在开始,经过多少纳秒
     2. queue   队列
     3. block   异步执行的任务

     dispatch_after 异步的

     应用场景:动画,钟摆,动画进行中,到左右两边后停留一下继续进行
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{

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

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

    /*
     打印结果证明是异步的:

     begin
     ----end-----<NSThread: 0x7f8192e04150>{number = 1, name = main}
     finished---><NSThread: 0x7f8192e04150>{number = 1, name = main}
     */
}

九. 一次性执行

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

// 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. 在内存中只有一个实例
  2. 提供一个全局的访问点

单例实现

// 使用 dispatch_once 实现单例
+ (instancetype)sharedSingleton {

    static id instance;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        instance = [[self alloc] init];
    });

    return instance;
}

sharedSingleton 方法经常用到

十. 调度组

常规用法

/*
    应用场景:
        将三个视频完全下载完毕才能继续后续的操作,这里就需要用到调度组对任务进行监控
 */

- (void)execGroupDispatch {

    NSLog(@"开始下载。。。--->%@", [NSThread currentThread]);

    // 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]);
    };

#warning enter 和 leave 这两个函数在调度组内部已经封装好了
    /** 调度组实现原理 */
    dispatch_group_enter(group);

    void (^task2) () = ^(){
        NSLog(@"正在下载中间部分--->%@", [NSThread currentThread]);

        [NSThread sleepForTimeInterval:3];

        NSLog(@"中间部分已经下载完毕--->%@", [NSThread currentThread]);

        /** 如果没有这句话,调度组不会识别已经结束,就不会执行拼接的操作 */
        dispatch_group_leave(group);
    };

    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(@"将下载的三段视频拼接中,马上播放!--->%@", [NSThread currentThread]);
    });

    /*
     快速添加到调度组:
        dispatch_group_async(group, q, ^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任务 1 %@", [NSThread currentThread]);
     });
     */

    /*
     打印结果:

      开始下载。。。---><NSThread: 0x7fa10ad06610>{number = 1, name = main}
      正在下载中间部分---><NSThread: 0x7fa10ae0f710>{number = 3, name = (null)}
      正在下载片头---><NSThread: 0x7fa10ae15650>{number = 2, name = (null)}
      正在下载片尾---><NSThread: 0x7fa10ad251b0>{number = 4, name = (null)}
      将下载的三段视频拼接中,马上播放!---><NSThread: 0x7fa10ad251b0>{number = 4, name = (null)}
     */
}

十一. GCD 栅栏

  • 当一批任务需要异步执行,但是需要分成两组,第一组执行完毕后才能执行第二组。这时候就需要用到 GCD 的栅栏方法 dispatch_barrier_async
- (void)barrierGCDTest {

    // 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

    // 异步执行
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"barrierTest:并发异步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"barrierTest:并发异步2   %@",[NSThread currentThread]);
        }
    });

    dispatch_barrier_async(queue, ^{
        NSLog(@"------------barrier------------%@", [NSThread currentThread]);
        NSLog(@"******* 并发异步执行,但是34一定在12后面 *********");
    });

    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"barrierTest:并发异步3   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"barrierTest:并发异步4   %@",[NSThread currentThread]);
        }
    });
}

上面代码的打印结果如下,开启了多条线程,所有任务都是并发异步进行。但是第一组完成之后,才会进行第二组的操作。

barrierTest:并发异步1   <NSThread: 0x60000026d740>{number = 3, name = (null)}
barrierTest:并发异步2   <NSThread: 0x60000026e480>{number = 6, name = (null)}
barrierTest:并发异步1   <NSThread: 0x60000026d740>{number = 3, name = (null)}
barrierTest:并发异步2   <NSThread: 0x60000026e480>{number = 6, name = (null)}
barrierTest:并发异步1   <NSThread: 0x60000026d740>{number = 3, name = (null)}
barrierTest:并发异步2   <NSThread: 0x60000026e480>{number = 6, name = (null)}
 ------------barrier------------<NSThread: 0x60000026e480>{number = 6, name = (null)}
******* 并发异步执行,但是34一定在12后面 *********
barrierTest:并发异步4   <NSThread: 0x60000026d740>{number = 3, name = (null)}
barrierTest:并发异步3   <NSThread: 0x60000026e480>{number = 6, name = (null)}
barrierTest:并发异步4   <NSThread: 0x60000026d740>{number = 3, name = (null)}
barrierTest:并发异步3   <NSThread: 0x60000026e480>{number = 6, name = (null)}
barrierTest:并发异步4   <NSThread: 0x60000026d740>{number = 3, name = (null)}
barrierTest:并发异步3   <NSThread: 0x60000026e480>{number = 6, name = (null)}

十一. 定时源事件和子线程运行循环

  • 触摸事件执行该方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    // 在后台执行该方法
    [self performSelectorInBackground:@selector(subThreadRun) withObject:nil];
}
  • 实现定时源方法
- (void)subThreadRun {

    NSLog(@"%s--->%@", __func__, [NSThread currentThread]);

    // 1. 创建计时器
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timeEvent) userInfo:nil repeats:YES];


    /**
     NSDefaultRunLoopMode 当拖动的时候,它会停掉
     因为这种模式是互斥的

     forMode:UITrackingRunLoopMode 只有输入的时候,它才会去执行定时器任务

     // 2.将计时器添加到运行循环中,只有加入到运行循环中,才知道有这个操作

     NSRunLoopCommonModes 包含了前面两种
     */
    //[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    //[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];


    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

    // 3. 一定要有这一步,因为子线程运行循环默认不开放
#warning 下载,定时源事件,输入源事件,如果放在子线程里面,如果想要它执行任务,就必须开启子线程的运行循环
    CFRunLoopRun();
}

// 递增打印
- (void)timeEvent {

    NSLog(@"%@--->%ld", [NSThread currentThread], i++);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容