提起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. ZigZag Conversion leetcode java

    题目: The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows l ...

  2. RCW 生存期控制

    RCW是.net 对com的包装,比如office就是通过RCW来被.net调用的.可以百度:office pia 了解更多信息. RCW 是一个.net类. 1.关闭线程对rcw的自动清理 Syst ...

  3. 配置Windows下的PHP开发环境

    一.配置 Apache 开发环境: 二.配置 PHP 开发环境 配置 Apache 开发环境 0. 下载 Apache.由于官方只提供了源码包,我们要么自己编译要么使用别人提供的已经编译好的二进制包. ...

  4. canvas弹动2

    在上一次的例子上加深一点点,这次我们选择动态的绑定目标点,三个目标相互转化,实现的思路是以拖拽点为目标计算出除了拖拽点以外的两个圆球的位置 以上个案例的公式,设点分别为A,B,C,当我拖拽点A时其余两 ...

  5. Asp.Net MVC<六>:Controller、Action 待续

    控制器 抽象类Controller Visual Studio的向导创建的Controller类型继承自抽象类Controller. 它是ControllerBase的子类. 实现了IControll ...

  6. 图解Javascript原型链

    本文尝试阐述Js中原型(prototype).原型链(prototype chain)等概念及其作用机制.上一篇文章(图解Javascript上下文与作用域)介绍了Js中变量作用域的相关概念,实际上关 ...

  7. C#-WinForm-发送邮件

    进入邮箱→打开设置→变为启用状态 发送前准备:发件人.发件人密码.收件人.标题.内容 在<发送>按钮中设置事件 一.引用 System.Net; 和 System.Net.Mail; 命名 ...

  8. <<< 数据库基础知识

    相关概念: 1.数据 (DATA) : 数据是描述现实世界事物的符号标记, 是指用物理符号记录下来的可以鉴别的信息. 包括:数字.文字.图形.声音.及其他特殊符号 2.数据库(DATABASE) :按 ...

  9. XML 架构 (XSD) 参考

    https://msdn.microsoft.com/zh-cn/library/ms256235.aspx XML 架构示例 XML 架构元素 XML 数据类型引用 XML 架构正则表达式 XML ...

  10. C# Httpclient编程

    今天研究了一天C#如何添加cookie到httpcient里面,从而发请求时,能把cookie作为头部发出,最后发现根本加不进去. Httpclient的cookie是来自上一个请求的响应,httpc ...