iOS用dispatch_group_t监听多个异步请求全部返回和多任务下载

提起ios中多个异步函数后的同步问题,自然会想到 dispatch group 这个概念,那么它能够解决异步网络请求的问题吗?

对于dispatch多个异步操作后的同步方法,以前只看过dispatch_group_async,看看这个方法的说明:

  • @discussion
  • Submits a block to a dispatch queue and associates the block with the given
  • dispatch group. The dispatch group may be used to wait for the completion
  • of the blocks it references.
    可以看出,dispatch_group_async,是用于同步工作的,但是,它的判断标准是放入的block是否执行完毕,如果我们放入block中包含异步的网络请求,这个方法无法在网络数据返回后再进行同步。

看一段使用dispatch_group_async处理网络问题的代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    NSURLSession *session = [NSURLSession sharedSession];
    
    dispatch_queue_t dispatchQueue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t dispatchGroup = dispatch_group_create();
    dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
        
        NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"got data from internet1");
        }];
        [task resume];
    });
    dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
        NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"got data from internet2");
        }];
        [task resume];
    });
    
    dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
        NSLog(@"end");
    });
}

看看log的输出:

2016-07-13 17:42:55.170 aaaa[4797:295528] end
2016-07-13 17:42:55.322 aaaa[4797:295574] got data from internet2
2016-07-13 17:42:55.375 aaaa[4797:295574] got data from internet1
完全没有达到效果。这是因为这里的网络请求是个异步的方法,没有等待具体的数据返回,放入的dispatch queue的 block就执行完毕了。所以没收到2个网络数据,就提前调用了dispatch_group_notify指定的结束方法。

看完了错误的方法,再看看正确的方法:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    NSURLSession *session = [NSURLSession sharedSession];
    
    dispatch_queue_t dispatchQueue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t dispatchGroup = dispatch_group_create();
    // dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
    
    dispatch_group_enter(dispatchGroup);
    
    NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"got data from internet1");
        
        dispatch_group_leave(dispatchGroup);
    }];
    [task resume];
    
    //  });
    //  dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
    dispatch_group_enter(dispatchGroup);
    
    NSURLSessionDataTask *task2 = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"got data from internet2");
        
        dispatch_group_leave(dispatchGroup);
    }];
    [task2 resume];
    // });
    
    dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
        NSLog(@"end");
    });
}

看正确是输出结果:

2016-07-13 17:46:10.282 aaaa[4847:300370] got data from internet1
2016-07-13 17:46:10.501 aaaa[4847:300370] got data from internet2
2016-07-13 17:46:10.502 aaaa[4847:300341] end
相对于简单的dispatch_group_async,dispatch_group_enter 和 dispatch_group_leave 可以对group进行更细致的处理。

我们看看关于dispatch_group_enter 的具体说明

  • Calling this function increments the current count of outstanding tasks in the group. Using this function (with dispatch_group_leave) allows your application to properly manage the task reference count if it explicitly adds and removes tasks from the group by a means other than using the dispatch_group_async function. A call to this function must be balanced with a call to dispatch_group_leave. You can use this function to associate a block with more than one group at the same time.

简单的说,就是dispatch_group_enter会对group的内部计数加一,dispatch_group_leave会对group的内部计数减一,就类似以前的retain和release方法。说白了也是维护了一个计数器。

以前我的做法就是自己维护计数器。在发送网络请求前,记下发送总数,数据返回后,在同一个thread中(或者在一个DISPATCH_QUEUE_SERIAL类型的dispatch_queue中),对计数器进行+1操作,当计数器和网络请求数相等时,调用最后的处理。

相比自己的处理的计数器,dispatch_group_enter 处理方法可能显得更正规一些,代码更规范了,但执行效果是一样的。。。

今天再改其他的工程的时候,又遇到了这个问题,有一个值,需要2个异步操作查询回2个值进行计算,因此必须再2个异步操作结束后才能进行计算操作。开始试着使用了OperationQueue,想用addDependency方法,但是这个方法无法灵活地控制,只适合block内容已经确定的情况。对于我遇到的这种异步操作,block的内容是不定的,需要依赖异步的返回,用operation queue会遇到各种问题,无法解决问题,十分复杂!

今天看到了dispatch_barrier_async函数,说明如下

  • Calls to this function always return immediately after the block has been submitted and never wait for the block to be invoked. When the barrier block reaches the front of a private concurrent queue, it is not executed immediately. Instead, the queue waits until its currently executing blocks finish executing. At that point, the barrier block executes by itself. Any blocks submitted after the barrier block are not executed until the barrier block completes.

  • The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_async function.

简单地说,就是在这个函数之前被提交到quque里的block一定会被先执行,之后执行dispatch_barrier_async设定的block,最后执行调用dispatch_barrier_async之后才提交到queue里的block。

开始以为这个函数能够处理我们的问题,结果是不行的,先看看测试用的代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    NSURLSession *session = [NSURLSession sharedSession];
    
    dispatch_queue_t dispatchQueue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
 
    dispatch_async(dispatchQueue, ^{
        NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"got data from internet1");
        }];
        
        [task resume];
    });
 
    dispatch_async(dispatchQueue, ^{
        NSURLSessionDataTask *task2 = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"got data from internet2");         
        }];
        [task2 resume];
    });
   
    dispatch_barrier_async(dispatchQueue, ^{
        NSLog(@"================== barrier block is called");
    });
    
    dispatch_async(dispatchQueue, ^{
        NSLog(@"=========the last block is called");
    });  
}

这段代码的输出结果是:

2016-07-17 14:34:46.620 aaaaa[5000:91010] ================== barrier block is called
2016-07-17 14:34:46.621 aaaaa[5000:91010] =========the last block is called
2016-07-17 14:34:46.815 aaaaa[5000:91010] got data from internet1
2016-07-17 14:34:46.866 aaaaa[5000:91014] got data from internet2
完全没有达到2个网络请求都返回后,再执行the last block的效果。

原因和 dispatch_group_async无法达到目的的原因是一样的:它认为一个block返回后就是逻辑结束了,就会继续执行其他代码,对于block中异步返回的网络数据,没有对应的处理手段。

NSUrlSession 不是用的 NSOperation Queue 吗,能不能直接利用Operation Queue 而不是dispatch_queue 来解决这个问题呢?

我们知道NSOperation中有addDependency这个方法,我们能不能把几个网络请求分别封装一下:

[NSBlockOperation blockOperationWithBlock:^{
        NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"got data from internet1");
        }];
        [task resume];
    }];

这样,再添加依赖,来达到效果呢?

结论是:仅仅使用NSBlockOperation来构建operation是不可以的。 这里的错误原因和使用dispatch_group_async是一样的。

但是,如果把NSUrlConnection的请求封装成NSOperation子类,使这个子类有这个效果:"当网络数据返回时,才算这个operation的结束",就可以利用这个子类和nsoperationqueue 达到我们的目的!

(题外话:NSOperationQueue 就不同于dispatch_queue了,它没有dispatch_queue中的并行,串行类型,但是,有个类似功能的属性maxConcurrentOperationCount,当maxConcurrentOperationCount = 1时,自然就是串行的了。)

总结使用urlsession 进行下载的通用方法,这个方法加入了对最大并发数的限制,也加入了全部完成后的回调,基本可以应对任何情况的下载了!

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);    
    dispatch_group_t dispatchGroup = dispatch_group_create();
    
    for (int i = 0; i < 10; i++) 
    {        
        NSLog(@"i is %d",i);
        NSURLSession *session = [NSURLSession sharedSession];
        NSURL *url = [NSURL URLWithString:@"https://codeload.github.com/EricForGithub/SalesforceReactDemo/zip/master"];
        NSURLSessionDownloadTask *sessionDownloadTask =[session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            sleep(5.0);
            NSLog(@"release a signal");
            dispatch_group_leave(dispatchGroup);
            dispatch_semaphore_signal(semaphore);
        }];
        
         dispatch_group_enter(dispatchGroup);//为了所有下载完成后能调用函数,引入 dispatch group。如果信号量是1的话,可以不使用这个机制,也能达到效果。
         dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //为了最大并发数,加入信号量机制
         [sessionDownloadTask resume];
    }
    
    dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
        NSLog(@"end");
    });   
}

参考来源:http://www.cnblogs.com/breezemist/p/5667776.html

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

推荐阅读更多精彩内容

  • 一:base.h 二:block.h 1. dispatch_block_flags:DISPATCH_BLOCK...
    小暖风阅读 2,423评论 0 0
  • Managing Units of Work(管理工作单位) 调度块允许您直接配置队列中各个工作单元的属性。它们还...
    edison0428阅读 7,967评论 0 1
  • NSThread 第一种:通过NSThread的对象方法 NSThread *thread = [[NSThrea...
    攻城狮GG阅读 797评论 0 3
  • 很久前的总结,今天贴出来。适合看了就用,很少讲解,纯粹用法。 目录 Dispatch Queue dispatch...
    和女神经常玩阅读 645评论 0 3
  • 1、主线程队列 VS 分线程队列 dispatch_sync 和 dispatch_async 区别: dispa...
    瑞小萌阅读 925评论 4 7