×

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

96
liang1991
2016.04.21 15:35* 字数 572

场景描述:我们同时发出了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的使用

iOS
Web note ad 1