GCD(三) dispatch_group

本文是GCD多线程编程中dispatch_group内容的小结,通过本文,你可以了解到:

  • 如何使用dispatch_group来实现在一系列并发任务完成后做一些收尾工作的需求

我们在平常的开发中,经常会遇到这样这样的一个需求,当应用程序启动时,需要从服务器获取各种配置信息,然后再去做首页UI的初始化与后面的逻辑处理。对于这个需求,我们肯定是希望可以调用一个方法来执行这些任务,并在所有网络请求完成后调用已完成的回调,用于后续UI的的初始化。

面对这种场景,我们可以使用一种最直接的方式,就是从第一个网络请求的回调中继续下一个网络请求,但是这样实现的话,我们的代码就会像这种:

                    }];
                }];
            }];
        }];
    }];

这种方式虽然也同样可以实现需求,但是在编码上不够优雅,没有太强的阅读性,其实,这种需要等待一系列并发任务完成,然后在完成之后做一些收尾的工作的需求,GCD已经提供了一组API,就是使用dispatch_group来做,dispatch_group的使用分为以下几步:

  1. 创建dispatch_group
  2. 添加任务(并发)到group中
  3. 添加监听group中任务结束时的回调

接下来,我们来具体看看dispatch_group相关的API与基本使用

测试代码在这

一、创建dispatch_group

dispatch_group_t

/*!
 * @typedef dispatch_group_t
 * @abstract
 * A group of blocks submitted to queues for asynchronous invocation.
 */
DISPATCH_DECL(dispatch_group);

dispatch_group_create

/*!
 * @function dispatch_group_create
 *
 * @abstract
 * Creates new group with which blocks may be associated.
 *
 * @discussion
 * This function creates a new group with which blocks may be associated.
 * The dispatch group may be used to wait for the completion of the blocks it
 * references. The group object memory is freed with dispatch_release().
 *
 * @result
 * The newly created group, or NULL on failure.
 */
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
DISPATCH_NOTHROW
dispatch_group_t
dispatch_group_create(void);

dispatch_group_t其实就是提交到队列中用以进行异步调用的一组任务

我们可以使用 dispatch_group_create方法来创建group

dispatch_group_t group = dispatch_group_create();

二、添加任务到dispatch_group

添加任务有2种方式:

  • 第一种是使用dispatch_group_async添加任务到一个特定的队列
  • 第二种是人为的告诉group,我们开始了一个任务(dispatch_group_enter),或者任务结束了(dispatch_group_leave

dispatch_group_async

#ifdef __BLOCKS__
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_group_async(dispatch_group_t group,
    dispatch_queue_t queue,
    dispatch_block_t block);
#endif /* __BLOCKS__ */

这个函数有3个参数,第一个是管理这些异步任务的group,第二个是用于提交异步任务队列,第三个是我们提交的任务。

使用这个方法,我们可以定制我们的网络请求任务,添加到对应的并发队列,然后使用group管理这些任务,这样我们的异步并发网络请求的目的就实现了。

但是,我们平时的开发过程中,我们的网络请求基本上都是需要异步添加任务的,无法直接使用队列,这时我们就可以使用dispatch_group_enterdispatch_group_leave`这一对API

dispatch_group_enter/leave

/*!
 * @function dispatch_group_enter
 *
 * @abstract
 * Manually indicate a block has entered the group
 *
 * @discussion
 * Calling this function indicates another block has joined the group through
 * a means other than dispatch_group_async(). Calls to this function must be
 * balanced with dispatch_group_leave().
 *
 * @param group
 * The dispatch group to update.
 * The result of passing NULL in this parameter is undefined.
 */
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_group_enter(dispatch_group_t group);

/*!
 * @function dispatch_group_leave
 *
 * @abstract
 * Manually indicate a block in the group has completed
 *
 * @discussion
 * Calling this function indicates block has completed and left the dispatch
 * group by a means other than dispatch_group_async().
 *
 * @param group
 * The dispatch group to update.
 * The result of passing NULL in this parameter is undefined.
 */
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_group_leave(dispatch_group_t group);

dispatch_group_enter表示这个任务已经添加到group中

dispatch_group_leave表示添加到group中的这个任务已经执行完成

我们经常会使用这组API,将一些异步的网络请求的任务包装起来放进group中(早版本的AFNetworking中执行异步任务):

NSLog(@"使用dispatch_group_enter方式追加任务3");
    dispatch_group_enter(self.group);

    //开启一个网络请求
    NSURLSession *session = [NSURLSession sharedSession];
    NSURL *url =
    [NSURL URLWithString:[@"https://www.baidu.com/" stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"GET";
    
    NSLog(@"3---start---%@",[NSThread currentThread]);
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"%@", [error localizedDescription]);
        }
        if (data) {
            NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
            NSLog(@"%@", dict);
        }
        NSLog(@"3---end---%@",[NSThread currentThread]);
        dispatch_group_leave(self.group);
    }];
    [dataTask resume];

这组API必须配对调用,否则,group中任务执行完成的指令永远不会调用。

三、添加监听group中任务结束时的回调

这里也有2种方式,dispatch_group_waitdispatch_group_notify

dispatch_group_wait

这种方式会阻塞当前的线程,直到group中的任务全部完成,程序才会继续往下执行。

dispatch_group_notify

这种方式是添加一个异步执行的任务作为结束任务,当group中的任务全部完成,才会执行dispatch_group_notify中添加的异步任务,这种方式不会阻塞当前线程,同时有一个单独的异步回调,代码组织性更好,使用也更新广泛一些。

四、代码实战 & 使用小结

接下来,我们看看完整的测试代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSLog(@"ZEDDispatchGroupViewController viewDidLoad");
    
    //第一步:创建group
    NSLog(@"初始化group");
    self.group = dispatch_group_create();
    
    //第二步:追加任务到group
    NSLog(@"使用dispatch_group_async方式追加任务1");
    dispatch_group_async(self.group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];                        // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
        NSLog(@"任务1完成");
    });
    
    NSLog(@"使用dispatch_group_async方式追加任务2");
    dispatch_group_async(self.group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];                        // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
        NSLog(@"任务2完成");
    });
    
    NSLog(@"使用dispatch_group_enter方式追加任务3");
    //dispatch_group_enter与dispatch_group_leave必须成对出现
    dispatch_group_enter(self.group);

    //开启一个网络请求
    NSURLSession *session = [NSURLSession sharedSession];
    NSURL *url =
    [NSURL URLWithString:[@"https://www.baidu.com/" stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"GET";
    
    NSLog(@"3---start---%@",[NSThread currentThread]);
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"%@", [error localizedDescription]);
        }
        if (data) {
            NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
            NSLog(@"%@", dict);
        }
        NSLog(@"3---end---%@",[NSThread currentThread]);
        NSLog(@"任务3完成");
        dispatch_group_leave(self.group);
    }];
    [dataTask resume];
    
    
    //第三步:添加group中任务全部完成的回调
    NSLog(@"使用dispatch_group_notify添加异步任务全部完成的监听");
    //dispatch_group_notify 的方式不会阻塞当前线程
    dispatch_group_notify(self.group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"---所有任务全部执行完毕---");
        
    });
    
    //dispatch_group_wai会阻塞当前线程,直到group中的任务全部完成,才能继续往主队列中追加任务
//    dispatch_group_wait(self.group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"---测试结束了---");
}

测试结果log如下:

2019-04-25 17:07:21.432220+0800 GCD(三) dispatch_group[28759:5272759] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
2019-04-25 17:07:21.543885+0800 GCD(三) dispatch_group[28759:5272759] ZEDDispatchGroupViewController viewDidLoad
2019-04-25 17:07:21.544044+0800 GCD(三) dispatch_group[28759:5272759] 初始化group
2019-04-25 17:07:21.544161+0800 GCD(三) dispatch_group[28759:5272759] 使用dispatch_group_async方式追加任务1
2019-04-25 17:07:21.544286+0800 GCD(三) dispatch_group[28759:5272759] 使用dispatch_group_async方式追加任务2
2019-04-25 17:07:21.544391+0800 GCD(三) dispatch_group[28759:5272759] 使用dispatch_group_enter方式追加任务3
2019-04-25 17:07:21.547318+0800 GCD(三) dispatch_group[28759:5272759] 3---start---<NSThread: 0x600002f86c00>{number = 1, name = main}
2019-04-25 17:07:21.548050+0800 GCD(三) dispatch_group[28759:5272759] 使用dispatch_group_notify添加异步任务全部完成的监听
2019-04-25 17:07:21.548173+0800 GCD(三) dispatch_group[28759:5272759] ---测试结束了---
2019-04-25 17:07:21.700314+0800 GCD(三) dispatch_group[28759:5272797] (null)
2019-04-25 17:07:21.700490+0800 GCD(三) dispatch_group[28759:5272797] 3---end---<NSThread: 0x600002fe0940>{number = 5, name = (null)}
2019-04-25 17:07:21.700611+0800 GCD(三) dispatch_group[28759:5272797] 任务3完成
2019-04-25 17:07:23.547004+0800 GCD(三) dispatch_group[28759:5272796] 1---<NSThread: 0x600002fde480>{number = 6, name = (null)}
2019-04-25 17:07:23.547076+0800 GCD(三) dispatch_group[28759:5272798] 2---<NSThread: 0x600002fde4c0>{number = 7, name = (null)}
2019-04-25 17:07:25.547612+0800 GCD(三) dispatch_group[28759:5272798] 2---<NSThread: 0x600002fde4c0>{number = 7, name = (null)}
2019-04-25 17:07:25.547634+0800 GCD(三) dispatch_group[28759:5272796] 1---<NSThread: 0x600002fde480>{number = 6, name = (null)}
2019-04-25 17:07:25.547901+0800 GCD(三) dispatch_group[28759:5272796] 任务1完成
2019-04-25 17:07:25.547910+0800 GCD(三) dispatch_group[28759:5272798] 任务2完成
2019-04-25 17:07:25.548138+0800 GCD(三) dispatch_group[28759:5272798] ---所有任务全部执行完毕---

dispatch_group_asyncdispatch_group_enter都是异步添加任务,不会阻塞当前线程

dispatch_group_notify不会阻塞当前线程,dispatch_group_wait会阻塞当前线程

dispatch_group_enterdispatch_group_leave必须成对出现,否则group中的任务永远不会完成

如果文中有错误的地方,或者与你的想法相悖的地方,请在评论区告知我,我会继续改进,如果你觉得这个篇文章总结的还不错,麻烦动动小手,给我的文章与Git代码样例点个✨

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

推荐阅读更多精彩内容

  • 本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法。这大概是史上最详细、清晰的关于 GCD 的详细讲...
    花花世界的孤独行者阅读 495评论 0 1
  • 博客链接深入理解GCD之dispatch_group 之前已经介绍了dispatch_semaphore的底层实现...
    NeroXie阅读 30,832评论 11 86
  • iOS多线程编程 基本知识 1. 进程(process) 进程是指在系统中正在运行的一个应用程序,就是一段程序的执...
    陵无山阅读 6,004评论 1 14
  • 修心何其重要,但外在举止又怎能无视? 我们的身体,也需要内外兼修~ 修“内”,也养“外”,才是表里如一,全面提升自...
    木冉Juli阅读 354评论 0 0
  • 参考书目:《你说话的温度,决定你人生的高度》 作者:[日] 斋藤孝 如何说话是一门学问。你的话里体现了你的气质,代...
    麦小麦的小岛阅读 177评论 1 7