提起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的输出

-- ::55.170 aaaa[:] end
-- ::55.322 aaaa[:] got data from internet2
-- ::55.375 aaaa[:] 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");
});
}

看正确是输出结果:

-- ::10.282 aaaa[:] got data from internet1
-- ::10.501 aaaa[:] got data from internet2
-- ::10.502 aaaa[:] 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");
}); }

这段代码的输出结果是:

-- ::46.620 aaaaa[:] ================== barrier block is called
-- ::46.621 aaaaa[:] =========the last block is called
-- ::46.815 aaaaa[:] got data from internet1
-- ::46.866 aaaaa[:] 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(); dispatch_group_t dispatchGroup = dispatch_group_create(); for (int i = ; i < ; 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_semaphore_signal(semaphore);
        
dispatch_group_leave(dispatchGroup);//调用后,有可能触发完成函数,所以应该在这之前先释放信号量,保证在完成函数里,所有的资源都释放了 }]; 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");
}); }

iOS 多个异步网络请求全部返回后再执行具体逻辑的方法的更多相关文章

  1. iOS开发——post异步网络请求封装

    IOS中有许多网络请求的函数,同步的,异步的,通过delegate异步回调的. 在做一个项目的时候,上网看了很多别人的例子,发现都没有一个简单的,方便的异步请求的封装例子.我这里要给出的封装代码,是异 ...

  2. iOS NSURLConnection和异步网络请求

    在日常应用中,我们往往使用AFNetworking等第三方库来实现网络请求部分.这篇文章会简要地介绍一下如何使用NSURLConnection来进行异步的网络请求. 我们先看一个小demo - (vo ...

  3. 多个网络请求成功返回再执行另外任务的思路分析(iOS)

    前言 今天我们来讨论一个经常出现的需求场景,也是一个老话题.在开发中我们往往会遇到需要进行多个网络请求,并且需要多个网络请求成功返回后再做其他事的场景.比如同一个界面显示的内容需要用到两个网络接口,而 ...

  4. ios 关于使用异步网络请求时block回调的内存注意

    在一个controller中,使用 NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest ...

  5. IOS9中使用NSURLConection发送异步网络请求

    IOS9中使用NSURLConection发送异步网络请求 在ios9中,NSURLConection的sendSync..和sendAsync已经过时.被NSURLSession代替. 以下蓝色部分 ...

  6. Android中的异步网络请求

    本篇文章我们来一起写一个最基本的Android异步网络请求框架,借此来了解下Android中网络请求的相关姿势.由于个人水平有限,文中难免存在疏忽和谬误,希望大家可以指出,谢谢大家:) 1. 同步网络 ...

  7. AJAX其实就是一个异步网络请求

    AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML).其实就是一个异步网络请求. 一.创建对象 var xmlhttp; if (w ...

  8. iOS AFNetWorking中block执行完后再执行其它操作

    需求:同时进行两次网络请求,网络请求是异步的,在网络请求成功后进行其它的操作.两个网络请求是这样,一个网络请求中block执行完之后,再进行其它操作,也是一样的原理,只是这时候不需要线程组了,只需要信 ...

  9. ajax请求执行完成后再执行其他操作(jQuery.page.js插件使用为例)

    就我们做知,ajax强大之处在于它的异步请求,但是有时候我们需要ajax执行彻底完成之后再执行其他函数或操作 这个时候往往我们用到ajax的回调函数,但是假如你不想或者不能把接下来的操作写在回调函数中 ...

随机推荐

  1. 关于Linux与Windows的区别的文章

    你从来只用过Windows,从来没接触过UNIX,只知道把一个文件拽来拽去,只知道硬盘就是C: D: E:却从来没有研究过分区表,也许 会用VC编个程序,很习惯它的集成环境.... 有一天,不管什么原 ...

  2. C语言编程实现Linux命令——who

    C语言编程实现Linux命令--who 实践分析过程 who命令是查询当前登录的每个用户,它的输出包括用户名.终端类型.登录日期及远程主机,在Linux系统中输入who命令输出如下: 我们先man一下 ...

  3. 给大家分享一个jQuery TAB插件演示

    jquery tab选项卡插件示例页面代码,使用jquery.tabs.js轻量级的tab选项卡插件来实现,并支持鼠标滑过.点击.自动切换.数据回调等功能,有的是点击切换,有的是鼠标滑过切换,自带了多 ...

  4. JS组件系列——不容错过的两款Bootstrap Icon图标选择组件

    前言:最近好多朋友在群里面聊到bootstrap icon图标的问题,比如最常见的菜单管理,每个菜单肯定需要一个对应的菜单图标,要是有一个可视化的图标选择组件就好了,最好是直接选择图标,就能得到对应的 ...

  5. Android开发的菜鸟小记

    1.主线程异常:添加网络连接: 2.权限异常: 3.空指针异常:NullException: 添加网络权限: DEBUG:Connected to the target VM, address: 'l ...

  6. 第一次使用Linux服务器所栽之坑

    一直以来想拥有一个服务器,但是一直舍不得.9.21日终于心血来潮,买了一个腾讯云服务器.对比了一下腾讯云和阿里云,发现腾讯云比较良心,不仅有学生价1元/月,而且整体价格也比阿里云便宜.之前用过一次阿里 ...

  7. less简介

    Less是一种动态的样式语言.Less扩展了CSS的动态行为,比如说,设置变量(Variables).混合书写模式(mixins).操作(operations)和功能(functions)等等,最棒的 ...

  8. download ncRNA sequences form NCBI

    #!/bin/bash usage() { echo;echo "Usage: ./`basename $0` [gi number list] [number of cpu]"; ...

  9. BZOJ 3230: 相似子串

    3230: 相似子串 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 1485  Solved: 361[Submit][Status][Discuss ...

  10. BZOJ 1878: [SDOI2009]HH的项链

    1878: [SDOI2009]HH的项链 Time Limit: 4 Sec  Memory Limit: 64 MBSubmit: 3548  Solved: 1757[Submit][Statu ...