场景
在开发过程中很常见的一个场景是一个页面需要调用两个异步的网络请求,需要等两个请求都返回以后再组合数据并刷新UI, 流程如下:
graph TD
A[client] --> B(Request)
B --> |One| C[Http request 1]
B --> |Two| D[Httep request 2]
C --> E[Merge data]
D --> E
E --> F[Update UI in main thread]
解决思路
等待两个线程结束后在主线程做一些事情, 可以很容易的想到用GCD任务组来实现,摘录以下OC代码片段,
1、dispatch_group_async
//
dispatch_group_t taskGroup=dispatch_group_create();
dispatch_queue_t mainQueue1=dispatch_get_main_queue();
//reload the table view on the main queue
dispatch_group_async(taskGroup, mainQueue1, ^{
//method 1
NSLog(@"method1-----");
});
dispatch_group_async(taskGroup, mainQueue1, ^{
//method 2
NSLog(@"method2----");
});
//at the end when we are done,dispatch the following block
dispatch_group_notify(taskGroup, mainQueue1, ^{
NSLog(@"finished");
[[[UIAlertView alloc] initWithTitle:@"Finished"
message:@"All tasks are finished" delegate:nil
cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show];
});
//we are done with the group
dispatch_release(taskGroup);
但是这个方法有一个问题,就是只能把两个同步方法组成的任务组的调用结果组合起来,而网络请求都是异步方法,dispatch_group_notify会在网络调用刚刚发起就触发了,不能等到网络调用的结果返回。
要再异步方法调用时阻塞,就需要用到信号量(dispatch_semaphore_t)了。
dispatch_semaphore_t
:通俗的说我们可以理解成他是一个红绿灯的信号,当它的信号量小于0时(红灯)等待,
当信号量为1或大于1时(绿灯)走。
swift3中的信号量使用
swift3发布后,全面取消了C风格的代码,因此GCD的调用方式也改变了。
创建,操作信号量的写法如下:
//创建初始值为0的信号量,这时代表是红灯
let sema = DispatchSemaphore(value: 0)
//让信号量 -1,比如默认值时0, wait()以后就变成了 -1了,因此会等待
sema.wait()
//让信号量 +1, 当>0时就代表绿灯可以走了
sema.signal()
所以我们的思路是:
1. 创建一个默认值为0的信号量,在调用网络请求的异步方法后就之行wait(),因此这个方法会一直等待, 在网络请求的block返回时之行signal(),这时才代表这个方法结束阻塞,可以继续执行了。
2. 两个网络请求方法都用同样的方式操作信号量,因此只有两个请求都返回结果后方法才会继续执行。
3. 再用原来的group.notify触发后续执行。
完整代码:
func loadData() {
showLoading()
//任务队列
let queue = DispatchQueue(label: "requestHandler")
//分组
let group = DispatchGroup()
weak var weakSelf = self
//第一个网络请求,查询群信息
queue.async(group: group) {
let sema = DispatchSemaphore(value: 0)
KRContactRequestHandler.sharedInstance.findGroupInfo(groupId: groupId, isLeaved: weakSelf!.isLeavedGroup(subsStatus: weakSelf!.subsStatus), success: { [weak self](json) in
//为model赋值
self?.viewModel = KRGroupInfoViewModel(json: json)
//信号量+1这时就可以触发这个任务结束了
sema.signal()
}, fail: { (errCode, errMsg) in
//网络调用失败也需要+1,否则会永远阻塞了
sema.signal()
print("load failed code:\(errCode) -- msg:\(errMsg)")
})
//异步调用返回前,就会一直阻塞在这
sema.wait()
}
//第二个网络请求,查询群成员信息,群聊群才需要查询
if isGroupChannel {
queue.async(group: group) {
let sema = DispatchSemaphore(value: 0)
KRContactRequestHandler.sharedInstance.queryGroupCardMembers(groupId: groupId, isLeaved: weakSelf!.isLeavedGroup(subsStatus: weakSelf!.subsStatus), pageNo: 1, success: {[weak self] (json) in
if let membersJSON = json["pageInfo"]["result"].array {
self?.members.removeAll()
for memberJSON in membersJSON {
let cardMember = KRGroupInfoViewModelCardMember(accountId: memberJSON["accountId"].stringValue, staffName: memberJSON["staffName"].stringValue, headIcon: memberJSON["headIcon"].stringValue.urlEncodeString())
self?.members.append(cardMember)
}
}
sema.signal()
}, fail: { (errCode, errMsg) in
sema.signal()
print("load failed code:\(errCode) -- msg:\(errMsg)")
})
sema.wait()
}
}
//全部调用完成后回到主线程,再更新UI
group.notify(queue: DispatchQueue.main, execute: {[weak self] in
//tableView同时用到了两个请求到返回结果
self?.tableView.reloadData()
self?.hideLoading()
})
}