提起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. 吉特仓库管理系统(开源)-如何在网页端启动WinForm 程序

    在逛淘宝或者使用QQ相关的产品的时候,比如淘宝我要联系店家点击旺旺图标的时候能够自动启动阿里旺旺进行聊天.之前很奇怪为什么网页端能够自动启动客户端程序,最近在开发吉特仓储管理系统的时候也遇到一个类似的 ...

  2. LCS问题

    最长公共子序列问题 1.这里pre数组表示回溯的方式,状态转移方程!!! #include <iostream> #include <algorithm> #include & ...

  3. Android四大组件之—— 使用服务进行后台操作

    什么是服务 服务是一个没有可视化界面的组件,它可以在后台长期运行并进行各种操作. 服务的创建 我们只需要继承Service类并实现相应的方法即可创建服务 要想启动服务,还得在AndroidManife ...

  4. 还原数据库:The backup set holds a backup of a database other than the existing database……

    还原数据库时报以上错误,解决办法是: 1.删除新建的DB 2.直接右键Databases-->Restore Database 3.在弹出窗口中的To database栏位填写需要新建的DB名称 ...

  5. java/python中的队列

    Queue<TreeNode> que=new LinkedList<>(); 用linkedlist实现队列,offer,poll进出队列,peek对列顶部元素 python ...

  6. sharedUserId

    android:sharedUserId sharedUserId的作用是让两个应用程序共享一个user id,我们都知道linux进程给每一个应用程序分配了一个独立的user id,所以如果两个或多 ...

  7. awk中gsub的应用

    (1)文件filename的内容 cat awk_file 1 2 3 $1,200.00 1 2 3 $2,300.00 1 2 3 $4,000.00 (2)去掉第四列的$和,并汇总第四列的和. ...

  8. c# WebClient Get Post 方法

    public string GetData(string url) { string data; using (var client = new WebClient()) { using (var s ...

  9. 【poj3875】 Lights

    http://poj.org/problem?id=3875 (题目链接) 题意 有M个N位的不同的二进制数,他们异或起来前v位等于1,求这m个数的不同组合方式(同一组数不同顺序不算). Soluti ...

  10. BZOJ4540 [Hnoi2016]序列

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000作者博客:http://www.cnblogs.com/ljh2000-jump/转 ...