如果一个页面需要发送4个网络请求,如何监听到所有的请求都已经回调之后再刷新UI?

Model里面有ABCD四个属性,这4个属性每个都不能直接用,都需要去调一个Block返回回来才能用,然后界面上的textview的值需要这4个属性来拼接,每个属性都有可能为空,最后拼出来的东西还不能打乱顺序。

看了一天的JavaScript基础语法,然后水了一下群,发现一个朋友在问这样的问题:

简单分析了一下,跟iOS一道面试题一样一样的

如果一个页面需要发送4个网络请求,如何监听到所有的请求都已经回调之后再刷新UI?

自己也曾经被问到过两次,当时都是说的声明一个全局变量在收到回调的时候+1,当这个变量等于4的时候再刷新UI。

现在看起来,这样的答案确实low得可怜。其实我是知道有dispatch group 这个东西的,但是,考虑到网络请求都是异步请求并且通过block回调的,那么block里面的内容无论有没有执行完毕,group notify 总是能够在真正受到回调才会刷新数据。

看到了朋友这样的问题,在群里简单的提了我用group的解决方案之后,主动提出了我写一个demo出来的建议,然后我就开始了

temp one

首先我模拟了4个任务分别延时了0.3 0.5 0.1 和马上返回; 然后是通过block回调通知接下来的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//任务A
- (void)requestA:(void (^)(NSString* ss))block {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_queue_create(0, 0), ^{
NSLog(@"aa");
block();
});
}
//任务B
- (void)requestB:(void (^)(NSString* ss))block {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_queue_create(0, 0), ^{
NSLog(@"bb");
block();
});
}
//任务C
- (void)requestC:(void (^)(NSString* ss))block {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_queue_create(0, 0), ^{
NSLog(@"cc");
block();
});
}
//任务D
- (void)requestD:(void (^)())block {
NSLog(@"dd");
block();
}

由于我要模拟的是在后台的情况下执行请求任务,所以我果断的讲dispatch中的dispatch_get_main_queue 改成了 dispatch_queue_create(0, 0)

然后我写了这样的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (void)sendRequestA {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
[self requestA:^(NSString* ss){
NSLog(@"%@",ss);
}];
});
dispatch_group_async(group, queue, ^{
[self requestB:^(NSString* ss){
NSLog(@"%@",ss);
}];
});
dispatch_group_async(group, queue, ^{
[self requestC:^(NSString* ss){
NSLog(@"%@",ss);
}];
});
dispatch_group_async(group, queue, ^{
[self requestD:^(NSString* ss){
NSLog(@"%@",ss);
}];
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"over");
});
}

我以为这个问题就解决了,然后我很自信的运行了一下

1
2
3
4
5
2016-07-27 16:00:51.268 IB[1944:308855] dd
2016-07-27 16:00:51.268 IB[1944:308823] over
2016-07-27 16:00:51.372 IB[1944:308823] cc
2016-07-27 16:00:51.602 IB[1944:308823] aa
2016-07-27 16:00:51.796 IB[1944:308823] bb

很显然我弄错了;

仔细阅读了一下代码和输出的值之后我意识到了,block回调是在notify之后执行的

temp two

然后我看了下意思的输入了disoatch_group。

等等disoatch_group_enter disoatch_group_wait disoatch_group_leave 这三个东西是什么鬼?
然后简单百度了一下,然后我就改了一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- (void)sendRequest {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self requestA:^(NSString* ss) {
dispatch_group_leave(group);
}];
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self requestB:^(NSString* ss) {
dispatch_group_leave(group);
}];
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self requestC:^(NSString* ss) {
dispatch_group_leave(group);
}];
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self requestD:^(NSString* ss) {
dispatch_group_leave(group);
}];
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"刷新UI");
});
}

再运行一下

1
2
3
4
5
2016-07-27 16:21:25.110 IB[2048:328327] ddd
2016-07-27 16:21:25.219 IB[2048:328327] ccc
2016-07-27 16:21:25.431 IB[2048:328327] aaa
2016-07-27 16:21:25.658 IB[2048:328327] bbb
2016-07-27 16:21:25.658 IB[2048:328295] 刷新UI

搞定!

temp three

最开始我在四个任务方法里面的dispatch_after都是Xcode 代码自动补全出来的,参数都是dispatch_get_main_queue() ,在第一次尝试的时候输出并没有什么不一样。

但是在第二次尝试的时候输出变成了

1
2016-07-27 16:23:29.547 IB[2062:330077] ddd

我想到了这应该是死锁造成的,因为在四个模拟任务中出了第四个dispatch_after都将回调放到主线程去了,而

1
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

也是在主线程wait的,考虑到UI的任务都是在主线程中执行的所以我在demo中引入了一个tableview,果然,wait的时候tableview也是卡死的,所以,呵呵。折腾了半天,还是没有找到合适的办法,我决定再深入的看一下文档;

然后我看了一下

这本书
看到了这句话

所以我将调用request的方法改了一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- (void)sendRequest {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self requestA:^(NSString* ss) {
NSLog(@"%@",ss);
dispatch_group_leave(group);
}];
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self requestB:^(NSString* ss) {
NSLog(@"%@",ss);
dispatch_group_leave(group);
}];
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self requestC:^(NSString* ss) {
NSLog(@"%@",ss);
dispatch_group_leave(group);
}];
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self requestD:^(NSString* ss) {
NSLog(@"%@",ss);
dispatch_group_leave(group);
}];
});
dispatch_async(dispatch_queue_create(0, 0), ^{
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"刷新UI");
});
});
}

问题完美解决。

temp four

后来我又想了一下,查了一下相关资料,写下了一下代码,然后发现这样也是可以的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
- (void)sendRequestB {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[self requestA:^(NSString *ss) {
NSLog(@"%@",ss);
dispatch_group_leave(group);
}];
});
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[self requestB:^(NSString *ss) {
NSLog(@"%@",ss);
dispatch_group_leave(group);
}];
});
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[self requestC:^(NSString *ss) {
NSLog(@"%@",ss);
dispatch_group_leave(group);
}];
});
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[self requestD:^(NSString *ss) {
NSLog(@"%@",ss);
dispatch_group_leave(group);
}];
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"over");
});
}

所以我大胆的得出了如果在block情况下使用dispatch_grop是需要在开始人物的时候enter 并且在结束任务的时候leave 这样的话使用dispatch_notify也是能够达到同样的效果的。

temp five

查阅了猿题库YTKNetwork相关代码之后,看到了YTKBatchRequest这个类,他的init方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (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;
}

并且这个类有一个property finishedCount

1
@property (nonatomic) NSInteger finishedCount;

而且这个property出了在initWithRequestArray中进行了原始赋值以外只在

1
- (void)requestFinished:(YTKRequest *)request;

这个方法里面使用到了。

大概猜到了YTKNetwork在处理批量网络请求的时候,应该是先调用initWithRequestArray这个初始化方法,把所有网络请求的实例放进去,然后再调用

1
2
3
4
5
- (void)startWithCompletionBlockWithSuccess:(void (^)(YTKBatchRequest *batchRequest))success
failure:(void (^)(YTKBatchRequest *batchRequest))failure {
[self setCompletionBlockWithSuccess:success failure:failure];
[self start];
}

这个方法开始举行网络请求,最后通过finishedCount这个熟悉来进行回调,如果有某一个失败了,那最后就返回失败巴拉巴拉…
这段写的有点水,主要是没有十分仔细的去阅读YTKNetwork的源码。大概看了一下,YTKNetwork的处理思路跟我之前的思路有些不谋而合,也是通过添加一个flag值来判断回调是否全部收到。不过经过猿题库团队的封装之后,这种问题的处理方式就变得更加的优雅了。

由于我demo只是写了几个简单的方法来模拟这种应用场景,所以我也简单的用YTK的思路来进行一下模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- (void)sendRequestC {
_requesrCount_ = 0;
// 模拟的数组
// NSArray *requestArray = @[@"A",@"B",@"C",@"D"];
dispatch_queue_t queue = dispatch_queue_create(0, 0);
dispatch_async(queue, ^{
[self requestA:^(NSString *ss) {
NSLog(@"%@",ss);
[self finishRequest];
}];
});
dispatch_async(queue, ^{
[self requestB:^(NSString *ss) {
NSLog(@"%@",ss);
[self finishRequest];
}];
});
dispatch_async(queue, ^{
[self requestC:^(NSString *ss) {
NSLog(@"%@",ss);
[self finishRequest];
}];
});
dispatch_async(queue, ^{
[self requestD:^(NSString *ss) {
NSLog(@"%@",ss);
[self finishRequest];
}];
});
}
- (void)finishRequest {
_requesrCount_ ++;
//由于没有模拟,所以没有使用array.count来判断
if (_requesrCount_ == 4) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"over");
});
}
}

结果是:

1
2
3
4
5
2016-07-29 14:58:23.068 IB[3403:280129] ddd
2016-07-29 14:58:23.169 IB[3403:280164] ccc
2016-07-29 14:58:23.390 IB[3403:280164] aa
2016-07-29 14:58:29.091 IB[3403:280164] bbb
2016-07-29 14:58:29.091 IB[3403:280129] over

好了第三种思路完成。

temp six

然后我又看到资料说GCD的信号量也能实现这种需求,看了一下相关资料,然后我是这写了这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
- (void)sendRequestD{
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_semaphore_t semapore = dispatch_semaphore_create(0);
dispatch_async(dispatch_queue_create(0, 0), ^{
[self requestA:^(NSString *ss) {
NSLog(@"%@",ss);
dispatch_semaphore_signal(semapore);
}];
});
dispatch_semaphore_wait(semapore, DISPATCH_TIME_FOREVER);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_semaphore_t semapore = dispatch_semaphore_create(0);
dispatch_async(dispatch_queue_create(0, 0), ^{
[self requestB:^(NSString *ss) {
NSLog(@"%@",ss);
dispatch_semaphore_signal(semapore);
}];
});
dispatch_semaphore_wait(semapore, DISPATCH_TIME_FOREVER);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_semaphore_t semapore = dispatch_semaphore_create(0);
dispatch_async(dispatch_queue_create(0, 0), ^{
[self requestC:^(NSString *ss) {
NSLog(@"%@",ss);
dispatch_semaphore_signal(semapore);
}];
});
dispatch_semaphore_wait(semapore, DISPATCH_TIME_FOREVER);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_semaphore_t semapore = dispatch_semaphore_create(0);
dispatch_async(dispatch_queue_create(0, 0), ^{
[self requestD:^(NSString *ss) {
NSLog(@"%@",ss);
dispatch_semaphore_signal(semapore);
}];
});
dispatch_semaphore_wait(semapore, DISPATCH_TIME_FOREVER);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"over");
});
}

很不幸:

1
2
3
4
2016-07-29 15:44:21.322 IB[3553:314160] ddd
2016-07-29 15:44:21.424 IB[3553:314355] ccc
2016-07-29 15:44:21.639 IB[3553:314355] aa
2016-07-29 15:44:21.842 IB[3553:314355] bbb

over是没有打印出来的,具体原因,我等我再仔细查找查找吧!

结语

没有结语,不知道具体实践中有没有其他更好的办法。如果有,也可以给我留言,我们一起讨论。

CepheusSun wechat
订阅我的公众号,每次更新我都不一定会告诉你!
坚持原创技术分享,您的支持将鼓励我继续创作!
0%