iOS处理同时回调多个请求结果——基于AFNetworking和GCD

实际工作中我们经常会遇到有接口需要同时返回请求结果的情况,比如某一个详情页,可能有详情信息和评论信息等多个接口需要请求,并且当多个接口全部完成的时候,刷新当前页面的数据,这里由于请求是异步的关系,我们不知道具体哪个请求会需要多久时间才能完成,所以今天分析一下解决方案。
在之前的《GCD的使用和原理》一文中有简单讲述这个问题,本文会针对这个问题详细讨论。
首先一个案例,如果存在多个异步处理,如何能知道同时完成呢,这里能想到的是使用dispatch_group_async的方式将异步处理放入一个组中,再使用dispatch_group_notify获得所有组中异步完成的通知回调。

    NSLog(@"全部开始-----%@", [NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        sleep(4);
        NSLog(@"子线程1-----%@", [NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        sleep(3);
        NSLog(@"子线程2-----%@", [NSThread currentThread]);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"全部结束-----%@", [NSThread currentThread]);
    });

这里简单模拟了异步的耗时操作,我们需要的结果是两个子线程的任务全部完成之后,才回到主线程继续任务,下面看一下打印结果

全部开始-----{number = 1, name = main}
子线程2-----{number = 5, name = (null)}
子线程1-----{number = 4, name = (null)}
全部结束-----{number = 1, name = main}

这里可以很明显的看出开始和结束都是主线程,而结束之前的确执行了两个子线程的耗时任务

这里实现了我们的需求,当然不只是一种方式可以实现,下面再尝试一种方法看看。

    NSLog(@"全部开始-----%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_async(queue, ^{
        sleep(4);
        NSLog(@"子线程1-----%@", [NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_async(queue, ^{
        sleep(3);
        NSLog(@"子线程2-----%@", [NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"全部结束-----%@", [NSThread currentThread]);

这次没有使用dispatch_group的方式,这个semaphore应该也比较常用了(如果不常用的可以看我之前的关于GCD那篇文章),我们使用信号量的方式,使用wait阻止主线程后续任务的开展,当信号量不为0的时候会-1并继续执行,如果信号量为0,则等待,在得到信号增加之前就会持续等待,后面参数为时间类型表示永久,当每个异步任务完成后,会给semaphore信号量+1,当所有任务完成之后,主线程不再被阻止,会继续任务。打印结果同上,这里不重复写了,好奇的小伙伴们可以试下。

以上使用了两种方式解决异步任务的全部完成通知,下面该解决本文的需求了,就是当多个请求全部完成的通知该怎么获得。
有些朋友好奇多个请求和多个异步任务有什么区别,这里解释下,多个异步任务我们可以指定这几个异步任务为同一个组的任务,以及通过组的notify方法得到任务结束的通知,但是多个请求没有这种条件,所以不能完全用之前的方式处理。
先给一下错误示范案例:

    NSLog(@"即将开始多个请求");
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_async(group, queue, ^{
        [[URLBase sharedInstance] getBusinessDataWithSuccess:^(NSDictionary *result) {
            NSLog(@"请求1完成");
        } failure:^(NSError *error) {
        }];
    });
    dispatch_group_async(group, queue, ^{
        [[URLBase sharedInstance] getHomeConfigurationWithSuccess:^(NSDictionary *result) {
            NSLog(@"请求2完成");
        } failure:^(NSError *error) {
        }];
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"全部完成");
    });

这里用了两个请求任务放入异步操作中,下面看打印结果是否和我们预想的一样

2018-09-11 22:43:04.469787+0800 Demo[2511:1215843] 即将开始多个请求
2018-09-11 22:43:04.472144+0800 Demo[2511:1216152] 全部完成
2018-09-11 22:43:05.598410+0800 Demo[2511:1215843] 请求1完成
2018-09-11 22:43:05.604425+0800 Demo[2511:1215843] 请求2完成

这里的时间很明显告诉我们,同时返回的通知并没有完成,原因是当我们使用请求的时候,相当于又一次开启了一个异步操作,请求的代码会立即执行完成,但是请求完成的异步回调并不能,所以我们要稍微改良一下我们的代码

NSLog(@"即将开始多个请求");
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_async(group, queue, ^{
        dispatch_group_enter(group);
        [[URLBase sharedInstance] getBusinessDataWithSuccess:^(NSDictionary *result) {
            dispatch_group_leave(group);
            NSLog(@"请求1完成");
        } failure:^(NSError *error) {
        }];
    });
    
    dispatch_group_async(group, queue, ^{
        dispatch_group_enter(group);
        [[URLBase sharedInstance] getHomeConfigurationWithSuccess:^(NSDictionary *result) {
            dispatch_group_leave(group);
            NSLog(@"请求2完成");
        } failure:^(NSError *error) {
        }];
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"全部完成");
    });

我们的改动很小,只有在请求之前加了enter函数,以及请求成功之后加入leave函数,下面看一下成功的结果

2018-09-11 22:48:41.626897+0800 Gemii[2518:1218253] 即将开始多个请求
2018-09-11 22:48:45.881502+0800 Gemii[2518:1218253] 请求1完成
2018-09-11 22:48:48.982129+0800 Gemii[2518:1218253] 请求2完成
2018-09-11 22:48:48.984066+0800 Gemii[2518:1218425] 全部完成

很明显我们的需求达到了,解决方案就是当请求之前调用dispatch_group_enter()函数,表明即将进入另一个内部操作中,后续任务暂时停止,直到请求成功之后,调用dispatch_group_leave()函数,表明另一个内部操作完成,可以进行接下来的操作,于是两个group_async同时完成,调用notify通知

上面很清楚我们用dispatch_group_enter和dispatch_group_leave这一对函数实现了我们的需求,下面仍然用semaphore的方式处理这个问题,不过semaphore如果像之前一样操作,同样会出问题,案例如下

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    NSLog(@"即将开始多个请求");

    [[URLBase sharedInstance] getBusinessDataWithSuccess:^(NSDictionary *result) {
        NSLog(@"请求1完成");
        dispatch_semaphore_signal(semaphore);
    } failure:^(NSError *error) {
    }];
    [[URLBase sharedInstance] getHomeConfigurationWithSuccess:^(NSDictionary *result) {
        NSLog(@"请求2完成");
        dispatch_semaphore_signal(semaphore);
    } failure:^(NSError *error) {
    }];
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"全部完成");

这里的代码也很清晰,和前文一样,我们认为当请求成功之后,将信号量+1,直到两个请求都完成解决wait的阻塞,但是我们看一下结果

2018-09-11 23:12:55.898884+0800 Gemii[2600:1231519] 即将开始多个请求

这里只打印了开始的部分,后续任务全部没有执行,并且页面进入了死锁状态,造成这个的原因是什么呢,看似和之前一样,应该没有问题,但是这里注意,我们主线程一开始就已经进入了wait状态,之前的方式是在异步的子线程中,增加信号量,但是我们这里想回到主线程的block已经被wait挡住不能执行,导致不能调用dispatch_semaphore_signal()函数增加信号量,所以造成了死锁,那么解决办法就是将请求放到异步队列中

NSLog(@"即将开始多个请求");
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        [[URLBase sharedInstance] getBusinessDataWithSuccess:^(NSDictionary *result) {
            NSLog(@"请求1完成---%@", [NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        } failure:^(NSError *error) {
        }];
    });
    dispatch_group_async(group, queue, ^{
        [[URLBase sharedInstance] getHomeConfigurationWithSuccess:^(NSDictionary *result) {
            NSLog(@"请求2完成---%@", [NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        } failure:^(NSError *error) {
        }];
    });
    
    dispatch_group_notify(group, queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"全部完成---%@", [NSThread currentThread]);
    });

这里我们虽然使用了group的方式,但是没有使用enter和leave两个函数,而是为了避免semaphore会在主线程死锁而加入了异步操作,下面是成功结果

2018-09-11 23:25:30.054214+0800 即将开始多个请求
2018-09-11 23:25:30.705859+0800 请求1完成---{number = 1, name = main}
2018-09-11 23:25:30.905989+0800 请求2完成---{number = 1, name = main}
2018-09-11 23:25:30.906441+0800 全部完成---{number = 11, name = (null)}

这样看就很清晰,两个请求完成是在主线程完成的回调,但是wait以及完成后操作,均在子线程中,不会对主线程造成阻塞,所以可以实现本文需求

到这里我使用了两种方式实现标题的需求,有更多的解决办法还期待大家的探索,这里就不多加赘述了,如果有更方便的方式,欢迎在下方评论区进行讨论,如果文章中有技术错误还请指正。
按照之前说的,后续文章会讨论多个请求同步执行的方法,最近时间比较紧,更新比较慢还请见谅,如果有认为本文有些作用的请点个赞支持下,感谢各方大佬~

你可能感兴趣的:(iOS处理同时回调多个请求结果——基于AFNetworking和GCD)