iOS GCD使用指南 數組
版權聲明:如需轉載,請註明出處,謝謝! 安全
http://blog.csdn.net/zhangao0086/article/details/38904923 多線程
Grand Central Dispatch(GCD)是異步執行任務的技術之一。通常將應用程序中記述的線程管理用的代碼在系統級中實現。開發者只須要定義想執行的任務並追加到適當的Dispatch Queue中,GCD就能生成必要的線程並計劃執行任務。因爲線程管理是做爲系統的一部分來實現的,所以可統一管理,也可執行任務,這樣就比之前的線程更有效率。 併發
Dispatch Queue app
Dispatch Queue是用來執行任務的隊列,是GCD中最基本的元素之一。 異步
Dispatch Queue分爲兩種: async
• Serial Dispatch Queue,按添加進隊列的順序(先進先出)一個接一個的執行 函數
• Concurrent Dispatch Queue,併發執行隊列裏的任務 spa
簡而言之,Serial Dispatch Queue只使用了一個線程,Concurrent Dispatch Queue使用了多個線程(具體使用了多少個,由系統決定)。 .net
能夠經過兩種方式來得到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」的輸出必定是在最後的:
[plain] view plain copy
print?
1 312
2 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的安全機制保證這段代碼只執行一次。