dispatch_semaphore

我为什么要用信号量?
因为一个需求。

需求介绍

这是一个很常见的需求:项目中的业务接口请求的时候需要Token验证。我们最简化这个需求就是:两个请求,请求1成功返回所需参数之后,才能开始请求2。

一个很容易想到的做法就是:

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
- (void)getToken
{
    //以上请求的设置忽略
    NSURLSessionDataTask *task = [mySession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data) {
            NSLog(@"get Token");
//拿到token,传给request请求做参数
            [self request:token];
        }else{
            NSLog(@"token error:%@",error.description);
        } 
    }];
    [task resume];
}

- (void)request:(NSString *)params
{
    //请求的设置忽略
    NSURLSessionDataTask *task = [mySession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data) {
            NSLog(@"request success");
        }else{
            NSLog(@"request error:%@----",error.description);
        }
    }];
    [task resume];
}

这种做法是最容易想到的,但是缺点也是很明显的。获取token的方法与业务层的请求紧密混合在一起了,而且很不容易分离,这会导致你的网络管理类的请求方法变得臃肿。

所以,下面我要用信号量使得token跟业务层的request完成分离开,使代码逻辑更清晰。

dispatch_semaphore 介绍

dispatch_semaphore只有三个方法:

1
2
3
4
5
6
//创建信号量
dispatch_semaphore_create
//发送信号量
dispatch_semaphore_signal
//等待信号量
dispatch_semaphore_wait

执行dispatch_semaphore_create 会根据传入的long型参数创建对应数目的信号量;执行 dispatch_semaphore_signal 会增加一个信号量;执行dispatch_semaphore_wait 则会减少一个信号量,如果信号量是0,就会根据传入的等待时间来等待。

这样一解释,不知道你有没有醍醐灌顶。对于上面的需求,我们在请求token的时候创建信号量为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
//请求的按钮点击
- (IBAction)buttonPress:(UIButton *)sender
{
//创建信号量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self getToken:semaphore];
//此时的信号量为0,只有token请求成功发送信号量之后,才会往下执行[self request]方法,否则会一直等下去;
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    [self request];
}

- (void)getToken:(dispatch_semaphore_t)semaphore
{
    //以上请求的设置忽略
    NSURLSessionDataTask *task = [mySession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data) {
            NSLog(@"get Token");
//成功拿到token,发送信号量:
            dispatch_semaphore_signal(semaphore);
        }else{
            NSLog(@"token error:%@",error.description);
        } 
    }];
    [task resume];
}

思路理顺了吧~让我们来跑一下程序:
哎呀~卡住了!。。。

是的,因为我们设置了永久等待,所以token请求异步去请求,在当前线程信号量是0,就不会往下执行了,会一直这样卡下去,该怎么解决呢?没错,用异步嘛~

再来代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

- (IBAction)buttonPress:(UIButton *)sender
{
//创建一个并行队列
    dispatch_queue_t queque = dispatch_queue_create("GoyakodCreated", DISPATCH_QUEUE_CONCURRENT);
    //异步执行
    dispatch_async(queque, ^{
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        [self getToken:semaphore];
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        [self request];
    });
    
    NSLog(@"main thread");
}

再跑一下程序,看控制台打印信息:

屏幕快照 2016-08-12 11.12.50.png

如果你还不放心可以多试几次,看看是不是token每次都在request之前。

总结

我们之所以要这样做,其实只有一个目的就是让代码清晰,这样获取Token的方法可以单独提出一个类,判断token是否过期等逻辑就不会跟业务请求混在一起了,毕竟真正项目运用的时候网络请求类要比demo复杂得多。

最后

demo 地址

文章目录
  1. 1. 需求介绍
  2. 2. dispatch_semaphore 介绍
  3. 3. 总结
  4. 4. 最后