在開發中很多時候會有這樣的場景,同一個界面有多個請求,而且要在這幾個請求都成功返回的時候再去進行下一操作,對于這種場景,如何來設計請求操作呢?今天我們就來討論一下有哪幾種方案。
分析:
在網絡請求的開發中,經常會遇到兩種情況,一種是多個請求結束后統一操作,在一個界面需要同時請求多種數據,比如列表數據、廣告數據等,全部請求到后再一起刷新界面。另一種是多個請求順序執行,比如必須先請求個人信息,然后根據個人信息請求相關內容。這些要求對于普通的操作是可以做到并發控制和依賴操作的,但是對于網絡請求這種需要時間的請求來說,效果往往與預期的不一樣。因為網絡請求是異步的,并不知道什么時候網絡請求。很多開發人員為了省事,對于網絡請求必須滿足一定順序這種情況,一般都是嵌套網絡請求,即一個網絡請求成功之后再請求另一個網絡請求,雖然采用嵌套請求的方式能解決此問題,但存在很多問題,如:其中一個請求失敗會導致后續請求無法正常進行、多個請求在時間上沒有復用,即無并發性。來看一下下面幾種方案:
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,這樣新的任務又要等待。
下面我們展示一段代碼來模擬同步請求:
從打印結果可以看出,在每個請求開始之前,我們創建一個信號量,初始為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
不會觸發。
下面我們展示一段代碼來模擬同步請求:
dispatch_group
會等和它關聯的所有的 dispatch_queue_t
上的任務都執行完畢才會發出同步信號,dispathc_group_notify
的代碼塊 block
會被執行。從控制臺的打印結構可以看出,如果將上面三個操作改成真實的網絡操作后,這個簡單的做法會變得無效,因為網絡請求需要時間,而線程的執行并不會等待請求完成后才真正算作完成,而是只負責將請求發出去,線程就認為自己的任務算完成了,當三個請求都發送出去,就會執行 dispathc_group_notify
中的內容,但請求結果返回的時間是不一定的,也就導致界面都刷新了,請求才返回,這就是無效的。
notify
的作用就是在 group
中的其他操作全部完成后,再操作自己的內容,所以我們會看到上面事件 A、B、C 執行之后,才執行事件 E。
和 dispatch_async
相比,當我們調用 n
次 dispatch_group_enter
后再調用 n
次 dispatch_group_level
時,dispatch_group_notify
和 dispatch_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
的線程之間依賴。