场景描述:我们同时发出了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(@"请求完成");
});
}
网友评论
2、你可以写个小工具类;举个例子,加个数组属性,新开一个网络请求就添加到这个数组里(可以再定义个类把url和参数字典存起来),然后请求结束后从这个数组里取出下一个请求发送(加个状态属性,标记是否正在进行网络请求,没有就发起网络请求,有就把请求相关的东西放到数组里...)
3、YTKNetwork里可能支持这个功能(很久没看了...记不太清楚)
其实关于那个请求失败的时候的代码感觉有可以优化的地方。
因为不管怎么样都是调用finish函数,这样界限就会有一点模糊。
方案:1.不如finish函数变成为success和 fail函数,独立出成功和失败的block回掉。
2.或者success和fail改成以delegate的形式进行回掉也会更清晰一点?