提起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. 基于Batcher比较器的双调排序网络

    1. Batcher比较器 Batcher比较器是指如果在两个输入端给定输入x,y,再在两个输出端输出最大值max{x,y}和最小值min{x,y}.如图1所示,我们规定Batcher比较器的上输出端 ...

  2. React基础知识

    学习文档(按优先级排列)http://reactjs.cn/react/docs/tutorial-zh-CN.htmlhttp://www.cnblogs.com/Mrs-cc/p/4969755. ...

  3. JS获取回车事件(兼容各浏览器)

    一.用到onkeydown获取事件动作, 二.用到键盘对应代码keyCode, 三. var event=arguments.callee.caller.arguments[0]||window.ev ...

  4. C#稳固基础:传统遍历与迭代器

    Hello,Coders.我们除了天天的码 if…else…之外,还会不断的码出foreach.我今天要说的是:传统遍历需实现的接口及我们还有一种更简洁优雅的方式实现多种迭代器. 传统遍历 传统的遍历 ...

  5. 项目分析_xxoo-master

    项目介绍:使用java1.5的原生xml操作类实现 对象<-->xml字符串的相互转化 项目分析:主要分为是三个部分 1.容器类:AbstractContainer         存储x ...

  6. 关于select那点事

    select: 通过监视多个文件描述符的数组.当select()返回后 文件描述符便会被内核修改标志位,使进程 能进行后续操作 ------------------------------------ ...

  7. 【USACO 2.4】Fractions to Decimals(分数转小数)

    题意:给你N/D的分数,让你输出等价的小数,如果是循环小数,用括号把循环节包起来.如果是整数,后面保留一位小数.每行最多输出76个字符. 题解:模拟除法,如果余数是第二次出现,则代表第一次出现的位置到 ...

  8. 使用canvas绘制一片星空

    效果图 五角星计算方式 代码 <body style="margin:0px;padding:0px;width:100%;height:100%;overflow:hidden;&q ...

  9. c# cache 缓存

    System.Web.Caching简介 /// <summary> /// 创建随机字符串 /// </summary> /// <returns></re ...

  10. 关于分页接口设计(下拉刷新上拉加载原理,解决page count请求重复数据的问题)