淺談如何在專案中處理頁面中的多個網路請求

sauce_xxoo發表於2017-10-24

在開發中很多時候會有這樣的場景,同一個介面有多個請求,而且要在這幾個請求都成功返回的時候再去進行下一操作,對於這種場景,如何來設計請求操作呢?今天我們就來討論一下有哪幾種方案。

分析:

在網路請求的開發中,經常會遇到兩種情況,一種是多個請求結束後統一操作,在一個介面需要同時請求多種資料,比如列表資料、廣告資料等,全部請求到後再一起重新整理介面。另一種是多個請求順序執行,比如必須先請求個人資訊,然後根據個人資訊請求相關內容。這些要求對於普通的操作是可以做到併發控制和依賴操作的,但是對於網路請求這種需要時間的請求來說,效果往往與預期的不一樣。因為網路請求是非同步的,並不知道什麼時候網路請求。很多開發人員為了省事,對於網路請求必須滿足一定順序這種情況,一般都是巢狀網路請求,即一個網路請求成功之後再請求另一個網路請求,雖然採用巢狀請求的方式能解決此問題,但存在很多問題,如:其中一個請求失敗會導致後續請求無法正常進行、多個請求在時間上沒有複用,即無併發性。來看一下下面幾種方案:

dispatch_semaphore 訊號量

訊號量是一個整數,在建立的時候會有一個初始值,這個初始值往往代表我要控制的同時操作的併發數。 在操作中,對訊號量會有兩種操作:訊號通知與等待。訊號通知時,訊號量會+1,等待時,如果訊號量大於0,則會將訊號量-1,否則,會等待直到訊號量大於0。什麼時候會大於零呢?往往是在之前某個操作結束後,我們發出訊號通知,讓訊號量+1。

在 GCD 中,提供了以下這麼幾個函式,可用於請求同步等處理,模擬同步請求:

// 建立一個訊號量(semaphore)
dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
// 等待,直到訊號量大於0時,即可操作,同時將訊號量-1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 訊號通知,即讓訊號量+1
dispatch_semaphore_signal(semaphore);
複製程式碼

在使用的時候,往往會建立一個訊號量,然後進行多個操作,每次操作都等待訊號量大於0再操作,同時訊號量-1,操作完後將訊號量+1。當訊號量就減小到0了,這時候wait操作會起作用,DISPATCH_TIME_FOREVER 表示會永遠等待,一直等到訊號量大於0,也就是有操作完成了,將訊號量+1了,這時候才可以結束等待,進行操作,並且將訊號量-1,這樣新的任務又要等待。

下面我們展示一段程式碼來模擬同步請求:

image.png

從列印結果可以看出,在每個請求開始之前,我們建立一個訊號量,初始為0,在請求操作之後,我們設一個 dispatch_semaphore_wait,在請求到結果之後,再將訊號量+1,也即是 dispatch_semaphore_signal。這樣做的目的是保證在請求結果沒有返回之前,一直讓執行緒等待在那裡,這樣一個執行緒的任務一直在等待,就不會算作完成,notify 的內容也就不會執行了,直到每個請求的結果都返回了,執行緒任務才能夠結束,這時候 notify 也才能夠執行。

dispatch_group(組)

可以使用 dispatch_group_async 函式將多個任務關聯到一個 dispatch_group 和相應的 queue 中,dispatch_group 會併發地同時執行這些任務。而且 dispatch_group 可以用來阻塞一個執行緒,直到 dispatch_group 關聯的所有的任務完成執行。有時候必須等待任務完成的結果,然後才能繼續後面的處理。

主要使用如下兩個函式:

dispatch_group_enter(group);
dispatch_group_leave(group);
複製程式碼

注意: 以上這兩個函式必須配對使用,否則 dispatch_group_notify 不會觸發。

下面我們展示一段程式碼來模擬同步請求:

image.png

dispatch_group 會等和它關聯的所有的 dispatch_queue_t 上的任務都執行完畢才會發出同步訊號,dispathc_group_notify 的程式碼塊 block 會被執行。從控制檯的列印結構可以看出,如果將上面三個操作改成真實的網路操作後,這個簡單的做法會變得無效,因為網路請求需要時間,而執行緒的執行並不會等待請求完成後才真正算作完成,而是隻負責將請求發出去,執行緒就認為自己的任務算完成了,當三個請求都傳送出去,就會執行 dispathc_group_notify 中的內容,但請求結果返回的時間是不一定的,也就導致介面都重新整理了,請求才返回,這就是無效的。

image.png

notify 的作用就是在 group 中的其他操作全部完成後,再操作自己的內容,所以我們會看到上面事件 A、B、C 執行之後,才執行事件 E。 和 dispatch_async 相比,當我們呼叫 ndispatch_group_enter 後再呼叫 ndispatch_group_level 時,dispatch_group_notifydispatch_group_wait 會收到同步訊號;這個特點使得它非常適合處理非同步任務的同步當非同步任務開始前呼叫 dispatch_group_enter 非同步任務結束後呼叫 dispatch_group_leve

NSOperationQueue

NSOperationQueue 只有兩種佇列,即主佇列和並行佇列。通過 [[NSOperationQueue alloc] init]; 建立的佇列都是並行佇列,並且可以將一個或多個 NSOperation 物件放到佇列中去執行,而且是非同步執行的,一個 NSOperation 物件可以通過呼叫 start 方法來執行任務,但是預設是同步執行的。則主佇列通過 [NSOperationQueue mainQueue]; 獲得,而且其中所有 NSOperation 都會在主執行緒中執行。

當然也可以利用 NSOperationQueue 的執行緒依賴,當某個 NSOperation 物件依賴於其它 NSOperation 物件的完成時,就可以通過 addDependency 方法新增一個或者多個依賴的物件,只有所有依賴的物件都已經完成操作,當前 NSOperation 物件才會開始執行操作。需要先新增依賴關係,再將操作新增到佇列中。另外,通過 removeDependency 方法來刪除依賴物件。

結論

在開發過程中,我們應儘量避免傳送同步請求;假設我們一個頁面需要同時進行多個請求,他們之間倒是不要求順序關係,但是要求等他們都請求完畢了再進行介面重新整理或者其他什麼操作。並且在某個操作依賴於其他幾個任務的完成時,採用 dispatch_group or dispatch_semaphore 來實現同步等處理。如果在某個操作依賴於其他幾個任務的完成,可以考慮使用 NSOperationQueue 的執行緒之間依賴。

相關文章