關于多個網絡請求同步的一些總結

等待多個并發請求同步回調

例如同時發起網絡請求A,網絡請求B,網絡請求C,需等待A,B,C都返回了才進行回調。曾經是利用 block 嵌套,就是A回調嵌B,B回調嵌C,十分膚淺的做法╮(╯▽╰)╭
這里介紹 GCD 中的兩種方法,第一種是利用 DispatchGroupenterleave,第二種是利用 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 函數。
這種方法的原理就是:

  1. 請求前 enter
  2. 請求完成后 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也是必須成對出現。
這種方法的原理就是:

  1. 創建0信號量。
  2. 請求完成釋放信號,使得信號量+1。
  3. 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_asyncdispatch_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 幾乎全部換新,具體使用必須看官方文件。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 談到iOS多線程,一般都會談到四種方式:pthread、NSThread、GCD和NSOperation。其中,蘋...
    攻城獅GG閱讀 284評論 0 3
  • 一. 重點: 1.dispatch_queue_create(生成Dispatch Queue) 2.Main D...
    BestJoker閱讀 1,594評論 2 2
  • Quality of Service(QoS) 這是在iOS8之后提供的新功能,蘋果提供了幾個Quality of...
    樹下老男孩閱讀 15,709評論 10 132
  • 英譯中 辦公室小談—談論天氣 梅利莎:嗨,米夏艾爾。快點進來,外面傾盆大雨。 米夏艾爾:奧, 你好,梅利莎。你也要...
    明睿周一方閱讀 347評論 0 0
  • 在她加我QQ之前,我跟她沒有什么交流,唯一說過的話就是問她有什么英語作業。后來高二快會考的前幾天晚上,我在玩手機,...
    miss豆芽兒閱讀 226評論 0 0