美文网首页iOS学习笔记iOS学习收录Network
iOS中多个网络请求的同步问题总结

iOS中多个网络请求的同步问题总结

作者: liang1991 | 来源:发表于2016-04-21 15:35 被阅读9584次

场景描述:我们同时发出了a、b、c 3个网络请求,我们希望在a、b、c 3个网络请求都结束的时候获得一个通知。
常见解决方法:通过度娘目前找到两种做法;1、通过添加标识来判断请求是否全部结束 2、dispatch_group + 信号量
本篇文章demo

1、添加标识的解决方法

在遇到这个问题时首先想到了唐巧大大的猿题库团队开源的网络框架YTKNetwork,然后阅读源码发现YTKNetwork是通过添加标识来实现网络请求的批量请求处理;
话不多说直接上代码在YTKNetwork里负责进行网络批处理请求的是YTKBatchRequest类,下面看下它的使用示例:

- (void)sendBatchRequest {
    GetImageApi *a = [[GetImageApi alloc] initWithImageId:@"1.jpg"];
    GetImageApi *b = [[GetImageApi alloc] initWithImageId:@"2.jpg"];
    GetImageApi *c = [[GetImageApi alloc] initWithImageId:@"3.jpg"];
    GetUserInfoApi *d = [[GetUserInfoApi alloc] initWithUserId:@"123"];
    YTKBatchRequest *batchRequest = [[YTKBatchRequest alloc] initWithRequestArray:@[a, b, c, d]];
    [batchRequest startWithCompletionBlockWithSuccess:^(YTKBatchRequest *batchRequest) {
        NSLog(@"succeed");
        NSArray *requests = batchRequest.requestArray;
        GetImageApi *a = (GetImageApi *)requests[0];
        GetImageApi *b = (GetImageApi *)requests[1];
        GetImageApi *c = (GetImageApi *)requests[2];
        GetUserInfoApi *user = (GetUserInfoApi *)requests[3];
        // deal with requests result ...
        NSLog(@"%@, %@, %@, %@", a, b, c, user);
    } failure:^(YTKBatchRequest *batchRequest) {
        NSLog(@"failed");
    }];
}

先调用初始化方法把4个网络请求的实例塞进去YTKBatchRequest *batchRequest = [[YTKBatchRequest alloc] initWithRequestArray:@[a, b, c, d]],看下这个初始化方法

- (id)initWithRequestArray:(NSArray *)requestArray {
    self = [super init];
    if (self) {
        _requestArray = [requestArray copy];
        _finishedCount = 0;
        for (YTKRequest * req in _requestArray) {
            if (![req isKindOfClass:[YTKRequest class]]) {
                YTKLog(@"Error, request item must be YTKRequest instance.");
                return nil;
            }
        }
    }
    return self;
}

我们看到有一个_finishedCount的变量根据字面很好理解是用来记录请求完成的个数,然后我们全局搜下这个变量,发现只有在下面的这个方法中用到了这个变量

- (void)requestFinished:(YTKRequest *)request {
    _finishedCount++;
    if (_finishedCount == _requestArray.count) {
        [self toggleAccessoriesWillStopCallBack];
        if ([_delegate respondsToSelector:@selector(batchRequestFinished:)]) {
            [_delegate batchRequestFinished:self];
        }
        if (_successCompletionBlock) {
            _successCompletionBlock(self);
        }
        [self clearCompletionBlock];
        [self toggleAccessoriesDidStopCallBack];
        [[YTKBatchRequestAgent sharedInstance] removeBatchRequest:self];
    }
}

上述方法是网络请求结束的回调代理方法,完成后_finishedCount计数加1,然后和保存网络请求实例的数组元素个数进行比较如果相等说明所有的请求都已经完成,调用回调的代理方法及block请求结束。

然后YTKNetwork对于批量网络请求失败的处理是,只要一个失败就立即停止请求,调用失败回调:

- (void)requestFailed:(YTKRequest *)request {
    [self toggleAccessoriesWillStopCallBack];
    // Stop
    for (YTKRequest *req in _requestArray) {//遍历请求实例数组
        [req stop];//停止请求
    }
    // Callback   //回调
    if ([_delegate respondsToSelector:@selector(batchRequestFailed:)]) {
        [_delegate batchRequestFailed:self];
    }
    if (_failureCompletionBlock) {
        _failureCompletionBlock(self);
    }
    // Clear
    [self clearCompletionBlock];
    
    [self toggleAccessoriesDidStopCallBack];
    [[YTKBatchRequestAgent sharedInstance] removeBatchRequest:self];
}

总结:YTKNetwork的做法大致就是用一个变量记录完成请求的个数,然后在单个网络请求结束回调的时候判断当前完成的网络请求个数是否和总的网络请求个数相等,如果相等则说明请求结束。

2、dispatch_group + 信号量

参考文章
参考文章采用的是group + 信号量,下面示例采用dispatch_group_enter、dispatch_group_leave实现详见 本篇文章demo

- (void)loadRequest1
{
    dispatch_group_t dispatchGroup = dispatch_group_create();
    dispatch_group_enter(dispatchGroup);
    [MALAFNManger getDataWithUrl:Url1 parameters:nil finish:^(RequestResult *result) {</br>
        
        NSLog(@"第一个请求完成");
        dispatch_group_leave(dispatchGroup);
        
    } des:@"第一个url"];
    
    dispatch_group_enter(dispatchGroup);
    [MALAFNManger getDataWithUrl:Url2 parameters:nil finish:^(RequestResult *result) {
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            sleep(10);//网络请求结束后回调是在主线程如果sleep放在外面会阻塞主线程
            NSLog(@"第二个请求完成");
            dispatch_group_leave(dispatchGroup);
        });
        
    } des:@"第二个url"];
    
    dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
        
        NSLog(@"请求完成");
    });
}

dispach_group的使用

相关文章

网友评论

  • 初衷难维:我试了一下用 group enter, group leave, group wait, 这种方法实现同步,任务1和任务2之间不能建立依赖,也就是不会1执行完,根据返回数据再执行2。但是这种方法应该适用于1,2任务之间没有依赖,可以分别独立执行,1,2执行完之后dispach_group给外界或主线程发送完成通知。我用的请求是AFN, 3.X, URLSession本来就是异步的。目前只有用回调嵌套解决实际业务场景了。
    liang1991:@初衷难维 嗯,是这样的。我的应用场景是,一个页面的数据来自多个接口,只有拿到所有接口的数据才可以展示页面,并不关心那个接口先返回数据。对于你这种多个请求有依赖的,需要嵌套调用的可以了解一下PromissKit之类的异步回调处理方法,代码结构看起来会好一些。
  • bbb1c4ff738d:多个异步请求怎么控制顺序,例如:a网线请求完成,b发起请求,b完成后c发起请求,有顺序?
    酷哥不回头看爆炸:可以使用信号量 来进行顺序控制
    liang1991:@红星卫 1、简单写就是a完成调b然后调c;
    2、你可以写个小工具类;举个例子,加个数组属性,新开一个网络请求就添加到这个数组里(可以再定义个类把url和参数字典存起来),然后请求结束后从这个数组里取出下一个请求发送(加个状态属性,标记是否正在进行网络请求,没有就发起网络请求,有就把请求相关的东西放到数组里...)
    3、YTKNetwork里可能支持这个功能(很久没看了...记不太清楚)
  • 3a0dd2a4c4c4:看到你写的Demo 了。也研究了一下。感觉收获蛮大。

    其实关于那个请求失败的时候的代码感觉有可以优化的地方。

    因为不管怎么样都是调用finish函数,这样界限就会有一点模糊。

    方案:1.不如finish函数变成为success和 fail函数,独立出成功和失败的block回掉。
    2.或者success和fail改成以delegate的形式进行回掉也会更清晰一点?

    liang1991:demo里为了简单我是在外面直接用了封装的网络请求方法,在实际应用中我一般会根据模块建一个请求接口管理文件,把请求都封装成api,在这个api里面做请求参数检查,返回结果处理,然后外面调用的地方拿到返回的请求对象后,如果成功就刷新UI展示页面,失败直接显示对象里的errorMsg就可以了。
    liang1991:@倲小陖 恩,这个看各人习惯吧,我用一个block返回一个对象出来,是为了返回足够的信息(却啥可以随便加不用改请求的方法),外面不用做过多处理直接展示就行了。至于你说的用block的形式会好一些,因为用delegate的话需要根据标识来区分网络请求。
  • hhgvg:有没有请求多个网络请求 刷新对应tableviewcell 上的进度
    liang1991:@hhgvg 这个跟网络请求同步应该是没什么关系,你这个需求应该是类似下载进度更新之类的,你请求发出的时候肯定会依据一个id什么的,然后请求返回时根据这个id取到你tableview数据源对应的数据,然后更新数据,刷新对应的row就可以了。
  • ZackLi:为什么第一个请求完成 第二个请求完成不会执行
  • DaZuo:调用了dispatch_group_enter就应该在调用对应的leave函数,这对于异步有错误就返回的情况就会有问题
    liang1991:@DaZuo 不太清楚你说的 “异步有错误就返回的情况就会有问题” 指的什么,能说详细点儿吗?

本文标题:iOS中多个网络请求的同步问题总结

本文链接:https://www.haomeiwen.com/subject/beytrttx.html