拨开GCD的面纱

作者: 哈哈大p孩 | 来源:发表于2016-08-01 11:44 被阅读785次

    之前我在博客中也说过了,关于GCD和block是很多新手的两大拦路虎,下面谈谈GCD。

    GCD是iOS中异步执行任务的技术之一,他可以生成必要的线程并计划执行任务,这样就可以通过管理线程的方式来实现操作,会比较有效率。

    先来写个最基本的GCD的用法

        dispatch_async(queue, ^{
            //长时间处理的东西,比如数据的访问等
           dispatch_async(dispatch_get_main_queue(), ^{
              //只在主线程可以执行的处理
           });
        });
    

    对上面代码不清楚的可以去看我这篇博客,最常用基本的:http://www.jianshu.com/p/c25bc3740848

    上面的就是在后台线程中执行长时间处理,处理结束时,主线程使用该处理结果结果的源代码,而queue就是队列,而我们开发者要做的就是定义想执行的任务并追加到适当的Dispatch Queue 中,比如下面的代码:

    dispatch_async(queue, ^{
         //想执行的任务
        });
    

    该源代码使用Blcok语法"定义想执行的任务",通过dispatch_async函数“追加”赋值在变量queue的“Dispatch Queue”中。仅这样就可使指定的Block在另一线程中执行。

    Dispatch Queue
    “Dispatch Queue”是什么呢?顾名思义,是执行处理的等待队列。我们通过dispatch_async函数等API,在Block语法中记述想执行的处理并将其追加到Dispatch Queue中。Dispatch Queue按照追加的顺序(先进先出FIFO)执行处理。

    而在执行处理时候存在两种Dispatch Queue,一种是等待现在执行中处理的Serial Dispatch Queue,另一种是不等待现在执行中处理的Concurrent Dispatch Queue (你可以理解前者是串行,后者可并行)

    举个例子:

    dispatch_async(queue, blk0);
    dispatch_async(queue, blk1);
    dispatch_async(queue, blk2);
    dispatch_async(queue, blk3);
    

    当queue为Serial Dispatch Queue(也就是串行),执行的顺序为:blk0,blk1,blk2,blk3,并分别等上一步完成再执行下一步。
    当queue为Concurrent Dispatch Queue(可以理解为并行),执行顺序为:blk0,blk1,blk2,blk3,但是当执行第一个的时候,他不会等待是否执行完成,就会立马执行第二个,如此重复。

    看到这里有的童鞋是不是有点混了呢?其实吧,上面说的两种(Serial Dispatch Queue,Concurrent Dispatch Queue)都是Dispatch Queue,我们现在回到这个问题上,如何才能得到Dispatch Queue(也就是上面的Serial Dispatch Queue,Concurrent Dispatch Queue),得不到说了那么多也是屁话。

    第一种方法是通过GCD的API生成Dispatch Queue。
    通过dispatch_queue_create函数可生成Dispatch Queue,下面的代码生成了Serial Dispatch Queue

    dispatch_queue_t mySerialDispath = dispatch_queue_create("mySerialDispath", NULL);
    

    生成Serial Dispatch Queue时,像该源代码这样,将第二个参数指定为NULL。而生成Concurrent Dispatch Queue时,代码如下,指定为DISPATCH_QUEUE_CONCURRENT

    dispatch_queue_t myConcurrentDispatch = dispatch_queue_create("myConcurrentDispatch", DISPATCH_QUEUE_CONCURRENT);
    

    在这里dispatch_queue_create函数的返回值为表示Dispatch Queue的“dispatch_queue_t类型”。之前的代码中所出现的变量queue均为dispatch_queue_t类型变量。
    在这里要说明一个问题,如果你部署的最低目标低于 iOS 6.0 or Mac OS X 10.8,那么需要通过release方法去释放Dispatch Queue,如下

    dispatch_release(mySerialDispath)
    

    当然了,现在一般都不用我们自己手动去释放了,系统一般也没那么低了

    第二种方法是获取系统标准提供的Dispatch Queue。
    实际上不用特意生成Dispatch Queue系统也会给我们提供几个。那就是Main Dispatch Queue和Global Dispatch Queue。
    Main Dispatch Queue正如其名称中含有的“Main”一样,是在主线程中执行的Dispatch Queue。因为主线程只有1个,所以Main Dispatch Queue自然就是Serial Dispatch Queue,追加到Main Dispatch Queue的处理在主线程的RunLoop中执行,由于在主线程中执行,因此要将用户界面的界面更新等一些必须在主线程中执行的处理追加到Main Dispatch Queue使用。
    另一个Global Dispatch Queue是所有应用程序都能够使用的Concurrent Dispatch Queue。没有必要再通过dispatch_queue_create函数逐个生成Concurrent Dispatch Queue,只要获取Global Dispatch Queue使用即可。
    另外,Global Dispatch Queue有4个优先级:High Priority,Default Priority,Low Priority,Background Priority(后台优先级)。
    下面举个例子来说明一下上面提到的自己创建和系统提供的Dispatch Queue。

    1.Serial Dispatch Queue:

    在viewdidload中运行下面代码

        dispatch_queue_t mySerialDispathOne = dispatch_queue_create("mySerialDispath", NULL);
        dispatch_async(mySerialDispathOne, ^{
            NSLog(@"1111111");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"第一个主线程调用");
            });
        });
        
        dispatch_queue_t mySerialDispathTwo = dispatch_queue_create("mySerialDispath", NULL);
        dispatch_async(mySerialDispathTwo, ^{
            NSLog(@"2222222");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"第二个主线程调用");
            });
        });
        
        dispatch_queue_t mySerialDispathThree = dispatch_queue_create("mySerialDispath", NULL);
        dispatch_async(mySerialDispathThree, ^{
            NSLog(@"33333333");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"第三个主线程调用");
            });
        });
    

    我运行了一次,打印如下:

    Paste_Image.png

    在这里我创建了三个串行的,但是都是不同队列的,然后打印出来却是随机的,说明多个Serial Dispatch Queue可并行执行。然后问题来了,如果我们创建了多个串行的,就会导致大量消耗内存,降低系统的响应功能。

    2.Concurrent Dispatch Queue

        dispatch_queue_t myConcurrentDispatch = dispatch_queue_create("myConcurrentDispatch", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(myConcurrentDispatch, ^{
            NSLog(@"11111");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"第一个主线程调用");
            });
        });
        
        dispatch_queue_t myConcurrentDispatchTwo = dispatch_queue_create("myConcurrentDispatch", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(myConcurrentDispatchTwo, ^{
            NSLog(@"222222");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"第二个主线程调用");
            });
        });
        
        
        dispatch_queue_t myConcurrentDispatchThree = dispatch_queue_create("myConcurrentDispatch", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(myConcurrentDispatchThree, ^{
            NSLog(@"333333");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"第三个主线程调用");
            });
        });
    

    运行后,结果也是随机的。

    3.Global Dispatch Queue

    代码如下:

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
            NSLog(@"低级顺序");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"low");
            });
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            NSLog(@"高级顺序");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"high");
            });
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"默认顺序");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"default");
            });
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
            NSLog(@"后台顺序");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"background");
            });
        });
    

    注意一下:优先级只是表示他们执行的优先级,DISPATCH_QUEUE_PRIORITY_HIGH表示优先级最高,最先执行,但并不代表他第一个执行完。

    4.dispatch_after

    用dispatch_after可以实现延迟执行的情况

        NSLog(@"1234");
        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC);
        dispatch_after(time, dispatch_get_main_queue(), ^{
            NSLog(@"waiter at least three seconds");
        });
    

    5.Dispatch Group

    说这个函数之前,我们先来看个例子,多个Concurrent Dispatch Queue运行,然后最后在主线程中也运行一条代码。

        dispatch_queue_t myConcurrentDispatch = dispatch_queue_create("myConcurrentDispatch", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(myConcurrentDispatch, ^{
            NSLog(@"11111");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"第一个主线程调用");
            });
        });
        
        dispatch_queue_t myConcurrentDispatchTwo = dispatch_queue_create("myConcurrentDispatch", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(myConcurrentDispatchTwo, ^{
            NSLog(@"222222");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"第二个主线程调用");
            });
        });
        
        
        dispatch_queue_t myConcurrentDispatchThree = dispatch_queue_create("myConcurrentDispatch", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(myConcurrentDispatchThree, ^{
            NSLog(@"333333");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"第三个主线程调用");
            });
        });
        
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"这个啥时候出现呢");
        });
    

    运行结果如下:


    Paste_Image.png

    我们发现最后一个写的主线程中的,并不是在最后执行 (当然了,如果用Serial Dispatch Queue是可以串行一个一个等待完成的)
    看如下代码:

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_async(group, queue, ^{
            NSLog(@"1111111");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"2222222");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"33333333");
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"这个肯定是最后输出");
        });
    

    运行的结果必然是最后一个输出,这就是使用Dispatch Group的原因。
    在追加到Dispatch Group中的处理全部执行结束时,该源代码中使用的dispatch_group_notify函数将执行的Block追加到Dispatch Group中,将第一个参数指定为要监视的Dispatch Group。在追加到该Dispatch Group的全部处理执行完毕后,将第三个参数的Block追加到第二个参数的Dispatch Queue中。在dispatch_group_notify函数中不管指定什么样的Dispatch Queue,属于Dispatch Group的全部处理在追加指定的Block时都已执行完毕。

    另外,在Dispatch Group中也可以使用dispatch_group_wait函数仅等待全部处理执行结束。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_async(group, queue, ^{
            NSLog(@"1111111");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"2222222");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"33333333");
        });
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER); //等待
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"这个肯定最后执行");
        });
    

    dispatch_group_wait函数的第二个参数指定为等待的时间(超时)。它属于dispatch_queue_t类型的值。该源代码使用DISPATCH_TIME_FOREVER,意味着永久等待。只要属于Dispatch Group的处理执行未结束,就会一直等待,中途不能取消。
    指定DISPATCH_TIME_NOW,则不用等待即可判定Dispatch Group的处理是否执行结束。

    dispatch_group_wait(group, DISPATCH_TIME_NOW);
    

    这样的话,在主线程的RunLoop的每次循环中,可检查执行是否结束 ,从而不耗费多余的等待时间,虽然这样也可以,但一般在这种情形下,还是推荐用dispatch_group_notify函数追加结束处理到Main Dispatch Queue中,这是因为dispatch_group_notify函数可以简化源代码。

    6.dispatch_barrier_async

    在访问数据库或者文件时候,向之前说的,使用Serial Dispatch Queue可避免数据竞争的问题。
    写入处理确实不可与其他的写入处理以及包含读取处理的其他某些处理并行执行。但是如果读取处理只是与读取处理并行执行,那么多个并行执行就不会发生问题。
    也就是说,为了高效率地进行访问,读取处理追加到Concurrent Dispatch Queue中,写入处理在任一个读取处理没有执行的状态下(在写入处理结束之前,读取处理不可执行)。
    在这里引出dispatch_barrier_async函数,它可以起到中间人的身份,让之前的任务处理完,先处理dispatch_barrier_async函数中的任务,再执行后面的。

     //创建一个txt文件
        NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
        NSString *path=[paths    objectAtIndex:0];
        NSLog(@"path = %@",path);
        NSString *filename=[path stringByAppendingPathComponent:@"junjieTest.txt"];
        NSFileManager* fm = [NSFileManager defaultManager];
        [fm createFileAtPath:filename contents:nil attributes:nil];
        
        //创建一个文字什么的,写到txt文件里
        NSString *str = @"111111";
        [str writeToFile:filename atomically:YES encoding:NSUTF8StringEncoding error:nil];
        NSString *myStr = [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:nil];
        NSLog(@"myStr == %@", myStr);
        
        dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{
            NSLog(@"one == %@", [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:nil]);
        });
        dispatch_async(queue, ^{
            NSLog(@"two == %@", [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:nil]);
        });
        dispatch_async(queue, ^{
            NSLog(@"three = %@", [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:nil]);
        });
        dispatch_async(queue, ^{
            NSLog(@"four == %@", [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:nil]);
        });
        //中间人
        dispatch_barrier_async(queue, ^{
            [@"333333" writeToFile:filename atomically:YES encoding:NSUTF8StringEncoding error:nil];
            NSLog(@"tttttttttt == %@",[NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:nil] );
        });
        
        //写在后面执行的
        dispatch_async(queue, ^{
            NSLog(@"five == %@", [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:nil]);
        });
        
        dispatch_async(queue, ^{
            NSLog(@"six == %@", [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:nil]);
        });
        
        dispatch_async(queue, ^{
            NSLog(@"seven == %@", [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:nil]);
        });
        
        dispatch_async(queue, ^{
            NSLog(@"eight == %@", [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:nil]);
        });
    

    打印结果如下:


    Paste_Image.png

    dispatch_barrier_async函数可以实现高效率的数据库访问和文件访问。

    7.dispatch_sync

    dispatch_sync函数,意味着同步,也就是将制定的block同步追加到指定的Dispatch Queue中,在追加Block结束之前,dispatch_sync函数会一直等待,也可以说是简易版的dispatch_group_wait函数。
    正因为dispatch_sync函数简单,所以容易造成死锁。
    如下:

    dispatch_sync(dispatch_get_main_queue(), ^{
             NSLog(@"dsggds");
         });
    

    该源代码在Main Dispatch Queue即主线程中执行指定的block,并等待其执行结束。而其实主线程中正在执行这些源代码,所以无法追加到Main Dispatch Queue的block。

    8.dispatch_apply

    该函数按指定的次数将指定的block追加到指定的Dispatch Queue中,并等待全部处理执行结束。

        dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
            NSLog(@"index == %lu", index);
        });
    

    打印结果如下:


    Paste_Image.png

    第一个参数为重复次数,第二个参数为追加对象的Dispatch Queue,第三个参数为追加的处理,第三个参数的block为带有参数的block,这是为了按第一个参数重复追加到block并区分各个block而使用。例如要对NSArray类对象的所有元素执行处理时,不必一个一个编写for循环部分。如下:

        NSArray *arr = @[@"1", @"2",@"3",@"4",@"5"];
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_apply([arr count], queue, ^(size_t index) {
            NSLog(@"%lu == %@", index, [arr objectAtIndex:index]);
        });
    

    另外,dipatch_apply函数也与dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步地执行dipatch_apply函数。

    9.dispatch_suspend / dispatch_resume

    当追加大量处理到Dispatch Queue时,在追加处理的过程中,有时希望不执行已追加的处理。
    在这种情况下,只要挂起Dispatch Queue即可。当可以执行时再恢复。
    dispatch_suspend函数挂起指定的Dispatch Queue。

    dispatch_suspend(queue);
    

    dispatch_resume函数恢复指定的Dispatch Queue。

    dispatch_resume(queue);
    

    这些函数对已经执行的处理没有影响。挂起后,追加到Dispatch Queue中但尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。代码如下:

        dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", NULL);
        dispatch_async(serialQueue, ^{
                for (int j = 0; j < 10; j++) {
                    NSLog(@"jjj == %d", j);
                    if (j == 2) {
                        dispatch_suspend(serialQueue);
                        NSLog(@"uuuuuuuuuuuuuuuuuuu分割线");
                    }
                    if (j == 7) {
                        dispatch_resume(serialQueue);
                    }
                }
        });
    

    打印如下:


    Paste_Image.png

    另外说一下,这两个函数应该是成对出现的,要不然程序会crash,不信你试试(反正我试过)

    10.dispatch_once

    写过单列的童鞋对它应该很熟悉了。
    dispatch_once函数是保证在应用程序执行中只执行一次指定处理的API。如下:

        static dispatch_once_t pred;
        dispatch_once(&pred, ^{
           //初始化
        });
    

    可以保证只执行一次初始化处理。

    除了上面说的,还有诸如Dispatch I/O提高文件读取速度等,有需要的可以自己研究研究(这个我也没怎么研究,好像是将文件大小分割从而进行读取)

    当然GCD还有很多更深层次的运用,比如Dispatch Source,这里也不说了(我也不会。。),有兴趣的可以自己去网上研究研究,我们掌握常见的Dispatch Queue就行了。

    花了几天时间断断续续也写差不多了,那GCD就给大家介绍到这里。如果你喜欢我写的文章,或者觉得对你的了解有帮助,就点个赞或者关注吧~谢谢。
    (参考书籍:Objective-C高级编程)

    相关文章

      网友评论

        本文标题:拨开GCD的面纱

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