?
# iOS GCD使用指南
Grand Central Dispatch(GCD)是異步執行任務的技術之一。一般將應用程序中記述的線程管理用的代碼在系統級中實現。開發者只需要定義想執行的任務并追加到適當的Dispatch Queue中,GCD就能生成必要的線程并計劃執行任務。由于線程管理是作為系統的一部分來實現的,因此可統一管理,也可執行任務,這樣就比以前的線程更有效率。
## Dispatch Queue
Dispatch Queue是用來執行任務的隊列,是GCD中最基本的元素之一。
Dispatch Queue分為兩種:
* Serial Dispatch Queue,按添加進隊列的順序(先進先出)一個接一個的執行
* Concurrent Dispatch Queue,并發執行隊列里的任務
簡而言之,Serial Dispatch Queue只使用了一個線程,Concurrent Dispatch Queue使用了多個線程(具體使用了多少個,由系統決定)。?
可以通過兩種方式來獲得Dispatch Queue,第一種方式是自己創建一個:
let?myQueue:?dispatch_queue_t?=?dispatch_queue_create("com.xxx",?nil)?
第一個參數是隊列的名稱,一般是使用倒序的全域名。雖然可以不給隊列指定一個名稱,但是有名稱的隊列可以讓我們在遇到問題時更好調試;當第二個參數為nil時返回Serial Dispatch Queue,如上面那個例子,當指定為**DISPATCH_QUEUE_CONCURRENT**時返回Concurrent Dispatch Queue。
需要注意一點,如果是在**OS X 10.8或iOS 6以及之后版本**中使用,Dispatch Queue將會由ARC自動管理,如果是在此之前的版本,需要自己手動釋放,如下:?
~~~
let?myQueue:?dispatch_queue_t?=?dispatch_queue_create("com.xxx",?nil)
dispatch_async(myQueue, { () -> Void?in
? ??println("in Block")
})
dispatch_release(myQueue)?
~~~
以上是通過手動創建的方式來獲取Dispatch Queue,第二種方式是直接獲取系統提供的Dispatch Queue。
要獲取的Dispatch Queue無非就是兩種類型:
* Main Dispatch Queue
* Global Dispatch Queue / Concurrent Dispatch Queue
一般只在需要更新UI時我們才獲取Main Dispatch Queue,其他情況下用Global Dispatch Queue就滿足需求了:
~~~
//獲取Main Dispatch Queue
let?mainQueue =?dispatch_get_main_queue()
//獲取Global Dispatch Queue
let?globalQueue =?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0)
~~~
得到的Global Dispatch Queue實際上是一個Concurrent Dispatch Queue,Main Dispatch Queue實際上就是Serial Dispatch Queue(并且只有一個)。
獲取Global Dispatch Queue的時候可以指定優先級,可以根據自己的實際情況來決定使用哪種優先級。
一般情況下,我們通過第二種方式獲取Dispatch Queue就行了。
## dispatch_after
dispatch_after能讓我們添加進隊列的任務延時執行,比如想讓一個Block在10秒后執行:?
~~~
var?time =?dispatch_time(DISPATCH_TIME_NOW, (Int64)(10?*?NSEC_PER_SEC))
dispatch_after(time, globalQueue) { () -> Void?in
? ??println("在10秒后執行")
}?
~~~
NSEC_PER_SEC表示的是秒數,它還提供了NSEC_PER_MSEC表示毫秒。
上面這句dispatch_after的真正含義是在10秒后把任務添加進隊列中,并不是表示在10秒后執行,大部分情況該函數能達到我們的預期,只有在對時間要求非常精準的情況下才可能會出現問題。
獲取一個dispatch_time_t類型的值可以通過兩種方式來獲取,以上是第一種方式,即通過dispatch_time函數,另一種是通過dispatch_walltime函數來獲取,dispatch_walltime需要使用一個timespec的結構體來得到dispatch_time_t。通常dispatch_time用于計算相對時間,dispatch_walltime用于計算絕對時間,我寫了一個把NSDate轉成dispatch_time_t的Swift方法:?
~~~
func?getDispatchTimeByDate(date:?NSDate) ->?dispatch_time_t?{
? ??let?interval = date.timeIntervalSince1970
? ??var?second =?0.0
? ??let?subsecond =?modf(interval, &second)
? ??var?time =?timespec(tv_sec:?__darwin_time_t(second), tv_nsec: (Int)(subsecond * (Double)(NSEC_PER_SEC)))
? ??return?dispatch_walltime(&time,?0)
}?
~~~
這個方法接收一個NSDate對象,然后把NSDate轉成dispatch_walltime需要的timespec結構體,最后再把dispatch_time_t返回,同樣是在10秒后執行,之前的代碼在調用部分需要修改成:?
~~~
var?time?=?getDispatchTimeByDate(NSDate(timeIntervalSinceNow:?10))
dispatch_after(time, globalQueue) { () -> Void?in
? ??println("在10秒后執行")
}
~~~
這就是通過絕對時間來使用dispatch_after的例子。
## dispatch_group
可能經常會有這樣一種情況:我們現在有3個Block要執行,我們不在乎它們執行的順序,我們只希望在這3個Block執行完之后再執行某個操作。這個時候就需要使用dispatch_group了:
let?globalQueue =?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0)
let?group =?dispatch_group_create()
dispatch_group_async(group, globalQueue) { () -> Void?in
? ??println("1")
}
dispatch_group_async(group, globalQueue) { () -> Void?in
? ??println("2")
}
dispatch_group_async(group, globalQueue) { () -> Void?in
? ??println("3")
}
dispatch_group_notify(group, globalQueue) ~~~
{ () -> Void?in
? ??println("completed")
}
~~~
輸出的順序與添加進隊列的順序無關,因為隊列是Concurrent Dispatch Queue,但“completed”的輸出一定是在最后的:
~~~
312??
completed??
~~~
除了使用dispatch_group_notify函數可以得到最后執行完的通知外,還可以使用
~~~
let?globalQueue =?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0)
let?group =?dispatch_group_create()
dispatch_group_async(group, globalQueue) { () -> Void?in
? ??println("1")
}
dispatch_group_async(group, globalQueue) { () -> Void?in
? ??println("2")
}
dispatch_group_async(group, globalQueue) { () -> Void?in
? ??println("3")
}
//使用dispatch_group_wait函數
dispatch_group_wait(group,?DISPATCH_TIME_FOREVER)
println("completed")
~~~
需要注意的是,dispatch_group_wait實際上會使當前的線程處于等待的狀態,也就是說如果是在主線程執行dispatch_group_wait,在上面的Block執行完之前,主線程會處于卡死的狀態。可以注意到dispatch_group_wait的第二個參數是指定超時的時間,如果指定為DISPATCH_TIME_FOREVER(如上面這個例子)則表示會永久等待,直到上面的Block全部執行完,除此之外,還可以指定為具體的等待時間,根據dispatch_group_wait的返回值來判斷是上面block執行完了還是等待超時了。
最后,同之前創建dispatch_queue一樣,如果是在**OS X 10.8或iOS 6以及之后版本**中使用,Dispatch Group將會由ARC自動管理,如果是在此之前的版本,需要自己手動釋放。
## dispatch_barrier_async
dispatch_barrier_async就如同它的名字一樣,在隊列執行的任務中增加“柵欄”,在增加“柵欄”之前已經開始執行的block將會繼續執行,當dispatch_barrier_async開始執行的時候其他的block處于等待狀態,dispatch_barrier_async的任務執行完后,其后的block才會執行。我們簡單的寫個例子,假設這個例子有讀文件和寫文件的部分:
~~~
func?writeFile() {
? ??NSUserDefaults.standardUserDefaults().setInteger(7, forKey:?"Integer_Key")
}
func?readFile(){
? ??print(NSUserDefaults.standardUserDefaults().integerForKey("Integer_Key"))
}?
~~~
寫文件只是在NSUserDefaults寫入一個數字7,讀只是將這個數字打印出來而已。我們要避免在寫文件時候正好有線程來讀取,就使用dispatch_barrier_async函數:?
~~~
NSUserDefaults.standardUserDefaults().setInteger(9, forKey:?"Integer_Key")
let?globalQueue =?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0)
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_barrier_async(globalQueue) {self.writeFile() ;?self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}
dispatch_async(globalQueue) {self.readFile()}?
~~~
我們先將一個9初始化到NSUserDefaults的Integer_Key中,然后在中間執行dispatch_barrier_async函數,由于這個隊列是一個Concurrent Dispatch Queue,能同時并發多少線程是由系統決定的,如果添加dispatch_barrier_async的時候,其他的block(包括上面4個block)還沒有開始執行,那么會先執行dispatch_barrier_async里的任務,其他block全部處于等待狀態。如果添加dispatch_barrier_async的時候,已經有block在執行了,那么dispatch_barrier_async會等這些block執行完后再執行。
## dispatch_apply
dispatch_apply會將一個指定的block執行指定的次數。如果要對某個數組中的所有元素執行同樣的block的時候,這個函數就顯得很有用了,用法很簡單,指定執行的次數以及Dispatch Queue,在block回調中會帶一個索引,然后就可以根據這個索引來判斷當前是對哪個元素進行操作:?
~~~
let?globalQueue =?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0)
dispatch_apply(10, globalQueue) { (index) -> Void?in
? ??print(index)
}
print("completed")?
~~~
由于是Concurrent Dispatch Queue,不能保證哪個索引的元素是先執行的,但是“completed”一定是在最后打印,因為dispatch_apply函數是同步的,執行過程中會使線程在此處等待,所以一般的,我們應該在一個異步線程里使用dispatch_apply函數:
~~~
let?globalQueue =?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0)
dispatch_async(globalQueue, { () -> Void?in
? ??dispatch_apply(10, globalQueue) { (index) -> Void?in
? ? ? ??print(index)
? ? }
? ??print("completed")
})
~~~
print("在dispatch_apply之前")?
## dispatch_suspend / dispatch_resume
某些情況下,我們可能會想讓Dispatch Queue暫時停止一下,然后在某個時刻恢復處理,這時就可以使用dispatch_suspend以及dispatch_resume函數:?
~~~
//暫停
dispatch_suspend(globalQueue)
//恢復
dispatch_resume(globalQueue)
~~~
暫停時,如果已經有block正在執行,那么不會對該block的執行產生影響。dispatch_suspend只會對還未開始執行的block產生影響。
## Dispatch Semaphore
信號量在多線程開發中被廣泛使用,當一個線程在進入一段關鍵代碼之前,線程必須獲取一個信號量,一旦該關鍵代碼段完成了,那么該線程必須釋放信號量。其它想進入該關鍵代碼段的線程必須等待前面的線程釋放信號量。
信號量的具體做法是:當信號計數大于0時,每條進來的線程使計數減1,直到變為0,變為0后其他的線程將進不來,處于等待狀態;執行完任務的線程釋放信號,使計數加1,如此循環下去。
下面這個例子中使用了10條線程,但是同時只執行一條,其他的線程處于等待狀態:
~~~
let?globalQueue =?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0)
let?semaphore =??dispatch_semaphore_create(1)
for?i?in?0?...?9?{
? ??dispatch_async(globalQueue, { () -> Void?in
? ? ? ??dispatch_semaphore_wait(semaphore,?DISPATCH_TIME_FOREVER)
? ? ? ??let?time?=?dispatch_time(DISPATCH_TIME_NOW, (Int64)(2?*?NSEC_PER_SEC))
? ? ? ??dispatch_after(time, globalQueue) { () -> Void?in
? ? ? ? ? ??print("2秒后執行")
? ? ? ? ? ??dispatch_semaphore_signal(semaphore)
? ? ? ? }
? ? })
}
~~~
取得信號量的線程在2秒后釋放了信息量,相當于是每2秒執行一次。
通過上面的例子可以看到,在GCD中,用dispatch_semaphore_create函數能初始化一個信號量,同時需要指定信號量的初始值;使用dispatch_semaphore_wait函數分配信號量并使計數減1,為0時處于等待狀態;使用dispatch_semaphore_signal函數釋放信號量,并使計數加1。
另外dispatch_semaphore_wait同樣也支持超時,只需要給其第二個參數指定超時的時候即可,同Dispatch Group的dispatch_group_wait函數類似,可以通過返回值來判斷。
這個函數也需要注意,如果是在**OS X 10.8或iOS 6以及之后版本**中使用,Dispatch Semaphore將會由ARC自動管理,如果是在此之前的版本,需要自己手動釋放。
## dispatch_once
dispatch_once函數通常用在單例模式上,它可以保證在程序運行期間某段代碼只執行一次,如果我們要通過dispatch_once創建一個單例類,在Swift可以這樣:
~~~
class?SingletonObject {
? ??class?var?sharedInstance :?SingletonObject?{
? ? ? ??struct?Static {
? ? ? ? ? ??static?var?onceToken :?dispatch_once_t?=?0
? ? ? ? ? ??static?var?instance :?SingletonObject? =?nil
? ? ? ? }
? ? ? ??dispatch_once(&Static.onceToken) {
? ? ? ? ? ??Static.instance =?SingletonObject()
? ? ? ? }
? ? ? ??return?Static.instance!
? ? }
}
~~~
這樣就能通過GCD的安全機制保證這段代碼只執行一次。
- 前言
- iOS 自定義頁面的切換動畫與交互動畫 By Swift
- Swift 元組(Tuples)介紹
- Swift 可選值(Optional Values)介紹
- Swift Switch介紹
- Swift 值類型和引用類型
- Swift 柯里化(Currying)
- iOS GCD使用指南
- iOS8 Core Image In Swift:自動改善圖像以及內置濾鏡的使用
- 讓Xcode自動更新Build版本
- Swift 全功能的繪圖板開發
- Swift Nullability and Objective-C
- Swift Core Data 圖片存儲與讀取Demo
- Swift 繪圖板功能完善以及終極優化
- 如何設計一個 iOS 控件?(iOS 控件完全解析)