美文网首页
iOS中GCD的深入浅出【图文并茂】

iOS中GCD的深入浅出【图文并茂】

作者: 白石洲霍华德 | 来源:发表于2017-03-03 18:07 被阅读166次

    1.什么是GCD

    全称是Grand Central Dispatch 可以理解为"牛逼的中枢调度器"
    纯C语言,提供了非常多非常强大的函数GCD比之 NSOpertionQueue更底层更高效,并且它不是Cocoa框架的一部分。

    2.GCD的优点

    易用性:GCD比NSthread简单易用,由于GCD是基于,work unit而并非像thread那样基于运算,导致GCD易用的原因有一部分在于你可以不用担心太多的效率问题而仅仅使用它就行了(就是告诉GCD我要执行什么任务,不需要编写任何线程管理代码)。

    效率高:GCD比较轻量,使得在很多地方比单独创建消耗内存的线程实用而且速度快。

    性能: GCD会自动根据系统负载来增减线程数量,这就减少了上下文切换以及增加了计算效率。

    2.GCD2个核心概念

    任务:执行什么操作
    队列:用来存放任务

    3.GCD的使用

    GCD的使用步骤其实很简单,只有两步。

    1. 创建一个队列(并行或者串行)
    2. 将任务添加到队列中,系统会根据任务类型来执行任务(任务只有异步和同步)

    创建队列

    • 可以创建dispatch_queue_create来创建对象,需要传入两个参数,第一个表示唯一表示符,用于DeBug,可为空;第二个参数用来识别是串行还是并行。DISPATCH_QUEUE_CONCURRENT表示并行,DISPATCH_QUEUE_SERIAL表示串行。
    //并行队列方法
     dispatch_queue_t  queue=dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
     //串行队列方法
     dispatch_queue_t  queue=dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    
    • 全局并发队
      本质是一个并发队列,由系统提供,方便编程,可以不用创建就直接使用
      获取全局队列的方法: dispatch_get_global_queue(long indentifier.unsigned long flags)
      参数说明:参数1:代表该任务的优先级,默认写0就行,不要使用系统提供的枚举类型,因为ios7和ios8的枚举数值不一样,使用数字可以通用。参数2:苹果保留关键字,一般也写0
    //全局并发队列
    dispatch_queue_t queue1=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    • 全局队列和并发队列的区别:
    1. 全局队列没有名字,但是并发队列有名字。有名字可以便于查看系统日志
    2. 全局队列是所有应用程序共享的。
    3. 在mrc的时候,全局队列不用手动释放,但是并发队列需要。

    任务创建

    • 同步执行任务
    dispatch_sync(queue, ^{
           NSLog(@"同步------%@",[NSThread currentThread]);
        });
    
    • 异步步执行任务
    dispatch_async(queue, ^{
           NSLog(@"异步------%@",[NSThread currentThread]);
        });
    

    队列+任务组合

    虽然使用GCD只需两步,但是既然我们有两种队列,两种任务执行方式,那么我们就有了四种不同的组合方式。这四种不同的组合方式是

    1. 并行队列 + 同步执行
    2. 并行队列+异步执行
    3. 串行队列+同步执行
    4. 串行队列+异步执行

    实际上,我们还有一种特殊队列是主队列,那样就有六种不同的组合方式了。

    1. 主队列 + 同步执行
    2. 主队列+异步执行

    这些组合的方式,得到的结果是这样的如图:

    并行队列 串行队列 主队列
    同步执行 不开启新的线程,串行执行任务 不开启新的线程,串行执行任务 不开启新的线程,串行执行任务
    异步执行 开启新的线程,并行执行任务 开启新的线程,并行执行任务 不开启新的线程,串行执行任务

    4.GCD的基本使用

    • 并行队列 + 同步执行

    不会开启新线程,执行完一个任务,再执行下一个任务

     NSLog(@"asyncConcurrent---begin");
        
        //创建并行队列方法
        dispatch_queue_t  queue=dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
        
        //创建同步执行任务
        dispatch_sync(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务1------%@",[NSThread currentThread]);
            }
        });
        dispatch_sync(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务2------%@",[NSThread currentThread]);
            }
        });
        dispatch_sync(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务3------%@",[NSThread currentThread]);
            }
        });
       
        NSLog(@"asyncConcurrent---end");
    

    执行结果

    2017-08-09 18:17:43.054 GCDdemo[6942:296736] asyncConcurrent---begin
    2017-08-09 18:17:43.054 GCDdemo[6942:296736] 任务1------<NSThread: 0x60800006ce80>{number = 1, name = main}
    2017-08-09 18:17:43.055 GCDdemo[6942:296736] 任务1------<NSThread: 0x60800006ce80>{number = 1, name = main}
    2017-08-09 18:17:43.055 GCDdemo[6942:296736] 任务2------<NSThread: 0x60800006ce80>{number = 1, name = main}
    2017-08-09 18:17:43.055 GCDdemo[6942:296736] 任务2------<NSThread: 0x60800006ce80>{number = 1, name = main}
    2017-08-09 18:17:43.055 GCDdemo[6942:296736] 任务3------<NSThread: 0x60800006ce80>{number = 1, name = main}
    2017-08-09 18:17:43.056 GCDdemo[6942:296736] 任务3------<NSThread: 0x60800006ce80>{number = 1, name = main}
    2017-08-09 18:17:43.056 GCDdemo[6942:296736] asyncConcurrent---end

    1. 并行队列 + 同步执行中可以看到,所有任务都是在主线程中执行的。由于只有一个线程,所以任务只能一个一个执行。
    2. 同时我们还可以看到,所有任务都在打印的syncConcurrent---beginsyncConcurrent---end之间,这说明任务是添加到队列中马上执行的
    • 并行队列 + 异步执行

    开启新线程,任务交替执行

     NSLog(@"asyncConcurrent---begin");
        
        //创建并行队列方法
        dispatch_queue_t  queue=dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
        
        //创建异步执行任务
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务1------%@",[NSThread currentThread]);
            }
        });
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务2------%@",[NSThread currentThread]);
            }
        });
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务3------%@",[NSThread currentThread]);
            }
        });
       
        NSLog(@"asyncConcurrent---end");
    

    执行结果

    2017-08-09 19:03:00.924 GCDdemo[7093:310628] asyncConcurrent---begin
    2017-08-09 19:03:00.925 GCDdemo[7093:310628] asyncConcurrent---end
    2017-08-09 19:03:00.925 GCDdemo[7093:310764] 任务1------<NSThread: 0x600000265640>{number = 3, name = (null)}
    2017-08-09 19:03:00.925 GCDdemo[7093:310765] 任务2------<NSThread: 0x608000462680>{number = 4, name = (null)}
    2017-08-09 19:03:00.925 GCDdemo[7093:310767] 任务3------<NSThread: 0x600000272fc0>{number = 5, name = (null)}
    2017-08-09 19:03:00.926 GCDdemo[7093:310764] 任务1------<NSThread: 0x600000265640>{number = 3, name = (null)}
    2017-08-09 19:03:00.928 GCDdemo[7093:310765] 任务2------<NSThread: 0x608000462680>{number = 4, name = (null)}
    2017-08-09 19:03:00.928 GCDdemo[7093:310767] 任务3------<NSThread: 0x600000272fc0>{number = 5, name = (null)}

    1. 并行队列 + 异步执行可以看出,除了主线程,程序有开启了3个线程,并且交替执行
    2. 另一方面可以看出,所有任务是在打印的syncConcurrent---beginsyncConcurrent---end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始异步执行
    • 串行队列 + 同步执行

    不会开启新的线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务

        NSLog(@"asyncConcurrent---begin");
        
        //串行队列方法
        dispatch_queue_t  queue=dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
        
        //创建同步执行任务
        dispatch_sync(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务1------%@",[NSThread currentThread]);
            }
        });
        dispatch_sync(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务2------%@",[NSThread currentThread]);
            }
        });
        dispatch_sync(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务3------%@",[NSThread currentThread]);
            }
        });
       
        NSLog(@"asyncConcurrent---end");
        
    

    输出结果

    2017-08-09 19:11:56.395 GCDdemo[7131:313993] asyncConcurrent---begin
    2017-08-09 19:11:56.396 GCDdemo[7131:313993] 任务1------<NSThread: 0x6080002617c0>{number = 1, name = main}
    2017-08-09 19:11:56.396 GCDdemo[7131:313993] 任务1------<NSThread: 0x6080002617c0>{number = 1, name = main}
    2017-08-09 19:11:56.397 GCDdemo[7131:313993] 任务2------<NSThread: 0x6080002617c0>{number = 1, name = main}
    2017-08-09 19:11:56.397 GCDdemo[7131:313993] 任务2------<NSThread: 0x6080002617c0>{number = 1, name = main}
    2017-08-09 19:11:56.398 GCDdemo[7131:313993] 任务3------<NSThread: 0x6080002617c0>{number = 1, name = main}
    2017-08-09 19:11:56.398 GCDdemo[7131:313993] 任务3------<NSThread: 0x6080002617c0>{number = 1, name = main}
    2017-08-09 19:11:56.400 GCDdemo[7131:313993] asyncConcurrent---end

    1. 串行队列 + 同步执行可以看到,所有任务都是在主线程中执行的,并没有开启新的线程。而且由于串行队列,所以按顺序一个一个执行
    2. 同时我们还可以看到,所有任务都在打印的syncConcurrent---beginsyncConcurrent---end之间,这说明任务是添加到队列中马上执行的
    • 串行队列 + 异步执行

    会开启新的线程(只会开启1条线程),但是因为任务是串行的,执行完一个任务,再执行下一个任务

     NSLog(@"asyncConcurrent---begin");
        
        //串行队列方法
        dispatch_queue_t  queue=dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
        
        //创建异步执行任务
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务1------%@",[NSThread currentThread]);
            }
        });
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务2------%@",[NSThread currentThread]);
            }
        });
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务3------%@",[NSThread currentThread]);
            }
        });
       
        NSLog(@"asyncConcurrent---end");
    
    1. 串行队列 + 异步执行可以看到,开启了一条新线程,但是任务还是串行,所以任务是一个一个执行。
    2. 另一方面可以看出,所有任务是在打印的syncConcurrent---beginsyncConcurrent---end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始同步执行。

    主队列

    1. 所有放在主队列中的任务,都会放到主线程中执行
    2. 可使用dispatch_get_main_queue()获得主队列
    • 主队列 + 同步执行

    互等卡住不可行(在主线程中调用)

        NSLog(@"asyncConcurrent---begin");
        
        //主队列
        dispatch_queue_t queue=dispatch_get_main_queue();
    
        
        //创建同步执行任务
        dispatch_sync(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务1------%@",[NSThread currentThread]);
            }
        });
        dispatch_sync(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务2------%@",[NSThread currentThread]);
            }
        });
        dispatch_sync(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务3------%@",[NSThread currentThread]);
            }
        });
       
        NSLog(@"asyncConcurrent---end");
        
        
    

    输出结果

    2017-08-09 19:25:43.049 GCDdemo[7199:320037] asyncConcurrent---begin

    程序为什么会挂了?

    崩了,主要原因是当前的队列是主队列,主队列中加了一个同步执行的任务。这个同步执行的任务必须要这个主队列执行完成,才执行,而主队列要执行完成必须要同步任务执行完成走主队列,也就是主队列===同步任务,同步任务====主队列,也就互相等,然后就GG了

    • 主队列 + 异步执行

    只在主线程中执行任务,执行完一个任务,再执行下一个任务

    //主队列
        dispatch_queue_t queue=dispatch_get_main_queue();
    
        
        //创建异步执行任务
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务1------%@",[NSThread currentThread]);
            }
        });
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务2------%@",[NSThread currentThread]);
            }
        });
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务3------%@",[NSThread currentThread]);
            }
        });
       
        NSLog(@"asyncConcurrent---end");
    
    

    输出结果

    asyncConcurrent---begin
    2017-08-09 19:36:08.925 GCDdemo[7255:323818] asyncConcurrent---end
    2017-08-09 19:36:08.942670 GCDdemo[7255:323818] subsystem: com.apple.BackBoardServices.fence, category: App, enable_level: 1, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 0, privacy_setting: 0, enable_private_data: 0
    2017-08-09 19:36:09.522 GCDdemo[7255:323818] 任务1------<NSThread: 0x600000069640>{number = 1, name = main}
    2017-08-09 19:36:09.523 GCDdemo[7255:323818] 任务1------<NSThread: 0x600000069640>{number = 1, name = main}
    2017-08-09 19:36:09.524 GCDdemo[7255:323818] 任务2------<NSThread: 0x600000069640>{number = 1, name = main}
    2017-08-09 19:36:09.524 GCDdemo[7255:323818] 任务2------<NSThread: 0x600000069640>{number = 1, name = main}
    2017-08-09 19:36:09.525 GCDdemo[7255:323818] 任务3------<NSThread: 0x600000069640>{number = 1, name = main}
    2017-08-09 19:36:09.525 GCDdemo[7255:323818] 任务3------<NSThread: 0x600000069640>{number = 1, name = main}

    1. 可以看出任务都是在主线程中执行完成,并没有开启新的线程,主要原因是主队列
    2. 从答应的结果看出先打印出syncConcurrent---begin和""syncConcurrent---end"",说明任务并不是马上执行,而是先把任务放入队列中再执行

    5.GCD线程通讯

    在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯

     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务1------%@",[NSThread currentThread]);
            }
            
            // 回到主线程
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"任务2-------%@",[NSThread currentThread]);
            });
        });
    
    

    输出结果

    2017-08-09 19:54:23.527 GCDdemo[7384:332728] 任务1------<NSThread: 0x600000070800>{number = 4, name = (null)}
    2017-08-09 19:54:23.528 GCDdemo[7384:332728] 任务1------<NSThread: 0x600000070800>{number = 4, name = (null)}
    2017-08-09 19:54:24.065 GCDdemo[7384:332634] 任务2-------<NSThread: 0x608000067e40>{number = 1, name = main}
    可以看出开启了其他线程执行任务,最后执行主线程

    6. GCD其他方法

    • GCD的界限方法

    dispatch_barrier_async
    有时候我们需要这种操作,我要分几组异步操作,第一组执行完了,第二组再执行,然后第三组再执行需要这种分组的操作就需要dispatch_barrier_async

    
    //并行队列
        dispatch_queue_t queue=dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
        //创建异步执行任务
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务1------%@",[NSThread currentThread]);
            }
        });
        
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务2------%@",[NSThread currentThread]);
            }
        });
        
        
        dispatch_barrier_sync(queue, ^{
            
            NSLog(@"==========分割线111111");
        });
        
        
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务3------%@",[NSThread currentThread]);
            }
        });
        
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务4------%@",[NSThread currentThread]);
            }
        });
        
        dispatch_barrier_sync(queue, ^{
            
            NSLog(@"==========分割线22222");
        });
        
        
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务5------%@",[NSThread currentThread]);
            }
        });
        
        dispatch_async(queue, ^{
            
            for (int i = 0; i < 2; ++i) {
                NSLog(@"任务6------%@",[NSThread currentThread]);
            }
        });
        
       
        NSLog(@"asyncConcurrent---end");
    
    

    输出结果

    2017-08-09 20:09:14.558 GCDdemo[7497:339896] asyncConcurrent---begin
    2017-08-09 20:09:14.559 GCDdemo[7497:340004] 任务2------<NSThread: 0x60800007c1c0>{number = 3, name = (null)}
    2017-08-09 20:09:14.559 GCDdemo[7497:340007] 任务1------<NSThread: 0x600000273440>{number = 4, name = (null)}
    2017-08-09 20:09:14.559 GCDdemo[7497:340004] 任务2------<NSThread: 0x60800007c1c0>{number = 3, name = (null)}
    2017-08-09 20:09:14.560 GCDdemo[7497:340007] 任务1------<NSThread: 0x600000273440>{number = 4, name = (null)}
    2017-08-09 20:09:14.560 GCDdemo[7497:339896] ==========分割线111111
    2017-08-09 20:09:14.561 GCDdemo[7497:340007] 任务3------<NSThread: 0x600000273440>{number = 4, name = (null)}
    2017-08-09 20:09:14.561 GCDdemo[7497:340004] 任务4------<NSThread: 0x60800007c1c0>{number = 3, name = (null)}
    2017-08-09 20:09:14.561 GCDdemo[7497:340007] 任务3------<NSThread: 0x600000273440>{number = 4, name = (null)}
    2017-08-09 20:09:14.562 GCDdemo[7497:340004] 任务4------<NSThread: 0x60800007c1c0>{number = 3, name = (null)}
    2017-08-09 20:09:14.562 GCDdemo[7497:339896] ==========分割线22222
    2017-08-09 20:09:20.701 GCDdemo[7497:339896] asyncConcurrent---end

    可以看出先执行分割线之前的,再执行分割线后的

    • GCD的延时执行方法

    dispatch_after
    当我们需要延迟执行一段代码时,就需要用到GCD的dispatch_after方法。

     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            //10秒后执行
            NSLog(@"啊哈哈");
            
        });
    
    • GCD的快速迭代方法

    ** dispatch_apply**
    通常便利一个数组什么的都是for循环,使用** dispatch_apply**更加高效

        
        NSArray  *array=@[@"哈哈",@"呵呵"];
        
        dispatch_apply(array.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
            
            
            NSLog(@"======%@",array[index]);
            
        });
    
    

    输出结果

    2017-08-09 20:29:55.843 GCDdemo[7895:354929] ======呵呵
    2017-08-09 20:29:55.843 GCDdemo[7895:354823] ======哈哈

    • GCD单例

    ** dispatch_once**
    我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了GCD的dispatch_once方法。使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行1次的代码(这里面默认是线程安全的)
    });
    
    • GCD的队列组

    dispatch_group
    有时候我们会有这样的操作,分别异步执行2个耗时操作,然后当2个耗时操作都执行完毕后再回到主线程执行操作。这时候我们可以用到GCD的队列组

    1. 先把线程放在队列里面,
    2. 再把队列放在队列组
      3.调用队列组的dispatch_group_notify回到主线程执行操作
    //队列组
        dispatch_group_t group=dispatch_group_create();
        
        
        //异步耗时操作
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            
            
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                
                for (int i = 0; i < 2; ++i) {
                    NSLog(@"任务1------%@",[NSThread currentThread]);
                }
            });
            
        });
        
        //异步耗时操作
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            
            
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                
                for (int i = 0; i < 2; ++i) {
                    NSLog(@"任务2------%@",[NSThread currentThread]);
                }
            });
            
        });
        
        
        //队列组回到主线程
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            
            NSLog(@"回答主线程");
            
        });
    

    输出结果

    2017-08-09 20:43:44.710 GCDdemo[8076:362721] 任务2------<NSThread: 0x60800007aa80>{number = 4, name = (null)}
    2017-08-09 20:43:44.710 GCDdemo[8076:362722] 任务1------<NSThread: 0x60800007a8c0>{number = 3, name = (null)}
    2017-08-09 20:43:44.711 GCDdemo[8076:362721] 任务2------<NSThread: 0x60800007aa80>{number = 4, name = (null)}
    2017-08-09 20:43:44.711 GCDdemo[8076:362722] 任务1------<NSThread: 0x60800007a8c0>{number = 3, name = (null)}
    2017-08-09 20:43:44.717 GCDdemo[8076:362623] 回答主线程

    小总结

    本文借鉴了不是前辈的经验,一点小总结希望可以帮助各位兄弟。一起快乐学习!!!


    哈哈.png

    相关文章

      网友评论

          本文标题:iOS中GCD的深入浅出【图文并茂】

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