等待多個并發請求同步回調
例如同時發起網絡請求A,網絡請求B,網絡請求C,需等待A,B,C都返回了才進行回調。曾經是利用 block
嵌套,就是A回調嵌B,B回調嵌C,十分膚淺的做法╮(╯▽╰)╭
這里介紹 GCD
中的兩種方法,第一種是利用 DispatchGroup
的 enter
和 leave
,第二種是利用 DispatchSemaphore
信號量。
以下代碼是基于 swift 3.0.2。
首先先創建一個模擬請求。
// 假裝是網絡請求
func networkRequest(sleepTime: Int, closure: @escaping ()->Void) -> Void {
DispatchQueue.global().async {
Thread.sleep(forTimeInterval: TimeInterval(sleepTime))
// 假裝是成功回調
closure()
}
}
第一種方法利用 enter&leave
。需要注意的是 enter&leave
必須成對出現,enter
少了會崩潰,leave
少了則永遠不會執行 notify
函數。
這種方法的原理就是:
- 請求前
enter
。 - 請求完成后
leave
。
// 利用 enter/leave 來控制
func gcd_group_enter_leave() {
let group = DispatchGroup.init()
let queue = DispatchQueue.global()
queue.async(group: group) {
group.enter()
print("1 start")
self.networkRequest(sleepTime:1, closure: {
print("1 end")
group.leave()
})
}
queue.async(group: group) {
group.enter()
print("2 start")
self.networkRequest(sleepTime:2, closure: {
print("2 end")
group.leave()
})
}
queue.async(group: group) {
group.enter()
print("3 start")
self.networkRequest(sleepTime:2, closure: {
print("3 end")
group.leave()
})
}
queue.async(group: group) {
group.enter()
print("4 start")
self.networkRequest(sleepTime:2, closure: {
print("4 end")
group.leave()
})
}
group.notify(queue: queue) { // 所有組完成后回調
print("all done")
}
}
第二種方法是利用信號量的 wait&signal
,簡單來說就是:signal
就是釋放信號即信號量 +1,wait
就是等待信號即信號量-1。wait&signal
也是必須成對出現。
這種方法的原理就是:
- 創建0信號量。
- 請求完成釋放信號,使得信號量+1。
-
notify
的回調操作前加入wait
操作(多少請求就加多少wait
,這要做為了得到足夠的信號量才能執行wait
下面的代碼)。
// 利用 semaphore 來控制
func gcd_semaphore_wait_signal() {
let semaphore = DispatchSemaphore.init(value: 0)
let group = DispatchGroup.init()
let queue = DispatchQueue.global()
queue.async(group: group) {
self.networkRequest(sleepTime:1, closure: {
print("1")
semaphore.signal()
})
}
queue.async(group: group) {
self.networkRequest(sleepTime:2, closure: {
print("2")
semaphore.signal()
})
}
queue.async(group: group) {
self.networkRequest(sleepTime:2, closure: {
print("3")
semaphore.signal()
})
}
group.notify(queue: queue) {
semaphore.wait()
semaphore.wait()
semaphore.wait()
print("all done")
}
}
多個相關請求順序執行
有時候開發中也會遇到利用網絡請求A返回的的數據來進行網絡請求B,以前也是十分膚淺地利用嵌套,雖然簡單可行而且可以減少中間變量,但是出現過多嵌套代碼不易debug。這里也是可以利用 DispatchSemaphore
信號量。網絡請求A完成后 signal
,而網絡請求B發起前 wait
。
// 利用 semaphore 來控制
func gcd_line_request() {
let semaphore = DispatchSemaphore.init(value: 0)
let group = DispatchGroup.init()
let queue = DispatchQueue.global()
queue.async(group: group) {
self.networkRequest(sleepTime:1, closure: {
print("1")
semaphore.signal()
})
}
queue.async(group: group) {
semaphore.wait()
self.networkRequest(sleepTime:2, closure: {
print("2")
})
}
}
當然也可以利用RAC,在A請求完成后發送信號喚醒B執行即可,還可以傳遞參數。
// 利用 rac 來控制
func rac_request() {
let (requestA, observerA) = Signal<Bool, NoError>.pipe()
self.networkRequest(sleepTime: 1) {
print("網絡請求A完成")
observerA.send(value: true) //網絡請求完成并且是成功的
observerA.sendCompleted()
}
requestA.observeValues { (success) in
if success {
self.networkRequest(sleepTime: 1, closure: {
print("網絡請求B完成")
})
}
}
}
順便說說柵欄函數 dispatch_barrier_async
這是可以在并行隊列中插入一個操作,例如任務1234都是并行的,如果在代碼中12和34插入dispatch_barrier_async
則可以保證 dispatch_barrier_async
中的內容在12后、34前執行。
func test() {
let group = DispatchGroup.init()
let queue = DispatchQueue.init(label: "xQ")
queue.async(group: group) {
print("1 start")
}
queue.async(group: group) {
print("2 start")
}
queue.async(group: group, flags: .barrier) {
print("5 start")
}
queue.async(group: group) {
print("3 start")
}
queue.async(group: group) {
print("4 start")
}
}
這里可以保證輸出為 12/21 5 34/43
這樣的順序,5肯定夾在中間。
不過注意這里有個十分坑的地方:對于 DispatchQueue.global()
這個函數不起效!
在 swift 文檔中并沒有提到,我是在看 OC 文檔中發現的解釋,對 DispatchQueue.global()
不起效,僅相當于 dispatch_async
,dispatch_barrier_async
只對自己創建的隊列才生效。
The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_async function.
最后說一句,swift3 中的 GCD
幾乎全部換新,具體使用必須看官方文件。