GCD、dispatch函數介紹

iOS多線程的方法有3種:html

  •  NSThread
  •  NSOperation
  •  GCD(Grand Central Dispatch)

其中,由蘋果所倡導的爲多核的並行運算提出的解決方案:GCD可以訪問線程池,而且可在應用的整個生命的週期裏面使用,通常來講,GCD會盡可能維護一些適合機器體系結構的線程,在有工做需求的時候,自動利用更多的處理器核心,以此來充分使用更強大的機器系統性能。在之前,iOS設備爲單核處理器的,線程池的用處並不大,可是如今的移動設備,包括iOS設備,愈發地朝多核的方向邁進,所以GCD中的線程池,可以在此類設備中,可以使得強大的硬件系統性能上獲得更加完善的利用。ios

           GCD,無疑是最便捷的,基於C語言的所設計的。在使用GCD的過程當中,最方便的,莫過於不須要編寫基礎線程代碼,其生命週期也不須要手動管理;建立須要的任務,而後添加到已建立好的queue隊列,GCD便會負責建立線程和調度任務,由系統直接提供線程管理。數據結構

這樣一種多線程的方式,咱們也會在實際項目中常常看到:app中,因爲數據的執行與交換所消耗的時間長,致使須要反饋給用戶UI界面每每出現延遲的現象。這樣咱們能夠經過多線程的方法,讓須要調用的方法在後臺執行、在主線程上進行UI界面的切換,這樣不只是用戶體驗更加友好美觀,也使得程序設計井井有理。多線程

本文主要粗略介紹GCD的通常使用,以及GCD中dispatch_前綴方法調用的做用和使用範圍。併發

 

 UI界面以下圖,經過建立4個按鈕事件,分析4種不一樣的函數所執行的程序塊運行方式:app

【本次開發環境: Xcode:7.2     iOS Simulator:iphone6   By:啊左】    iphone

 

1、GCD的使用異步

GCD對於開發者來講,最簡單的,就是經過調用dispatch把一連串的異步任務添加到隊列中,進行異步執行操做。async

代碼調用以下:函數

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
  • async表示異步運行;  
  • queue爲咱們提早建立的隊列;    
  • block也就是「塊」,讓咱們執行事件的模塊;

async(異步)與sync(同步):

固然,咱們也可使用同步任務,使用dispatch_sync函數添加到相應的隊列中,而這個函數會阻塞當前調用線程,直到相應任務完成執行。

可是,也正由於這樣的同步特性,在實際項目中,當有同步任務添加到正在執行同步任務的隊列時,串行的隊列會出現死鎖。並且因爲同步任務會阻塞主線程的運行,可能會致使某個事件沒法響應。

隊列(queue):

須要注意的是,調用dispatch_async不會讓塊運行,而是把塊添加到隊列末尾。隊列不是線程,它的做用是組織塊。(若是讀者學過數據結構的知識,就會知道隊列的基本特徵如飯堂排隊隊,先到的排前面,先打到飯,也就是「先進先出」原理)

在GCD中,能夠給開發者調用的常見公共隊列有如下兩種:

  1. dispatch_get_global_queue:用於獲取應用全局共享的併發隊列 (提供多個線程來執行任務,因此能夠按序啓動多個任務併發執行。可用於後臺執行任務)
  2. dispatch_get_main_queue:   用於獲取應用主線程關聯的串行調度隊列(只提供一個線程執行任務。運行的main主線程,通常用於UI的搭建)

(還有另一種,dispatch_get_current_queue,用於獲取當前正在執行任務的隊列,主要用於調試,可是iOS 6.0以後蘋果已經廢棄,緣由是容易形成死鎖。詳情能夠查看官方註釋。)

這兩種公共隊列的調用即可以解決咱們剛剛關於後臺執行任務、主線程用於更新UI界面的問題,

結構以下:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 把邏輯計算等須要消耗長時間的任務,放在此處的全局共享的併發隊列執行;
        dispatch_async(dispatch_get_main_queue(), ^{ // 回到主線程更新UI界面;
 }); });

例如在有一些項目中,會涉及到異步下載圖片,這個時候就可使用這樣一種結構來進行任務的分配:

// 異步下載圖片
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //先把下載數據的任務放在全局共享併發隊列中執行
        NSURL *url = [NSURL URLWithString:@"圖片的URL"]; NSData * data = [[NSData alloc]initWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; if(data != nil) { // 完成後,回到主線程顯示圖片
            dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); } });

 

2、 串行隊列 and  並行隊列 

1.串行(Serial)的執行:指同一時間每次只能執行一個任務。 線程池只提供一個線程用來執行任務,因此後一個任務必須等到前一個任務執行結束才能開始。

能夠添加多個任務到隊列中,執行次序FIFO,可是當程序須要執行大量的任務時,雖然系統容許,可是鑑於程序的資源分配,應該交給全局併發隊列來完成才能更好地發揮系統性能。

建立串行隊列的方式以下:

dispatch_queue_t serialQueue = dispatch_queue_create("zuoA", NULL); //第一個參數是隊列的名稱,一般使用公司的反域名;第二個參數是隊列相關屬性,通常用NULL.

關於什麼是FIFO次序,咱們用代碼解釋一下

- (IBAction)SerialQueue:(UIButton *)sender { dispatch_queue_t serialQueue = dispatch_queue_create("zuoA", NULL); dispatch_async(serialQueue, ^{ sleep(3); NSLog(@"A任務"); }); dispatch_async(serialQueue, ^{ sleep(2); NSLog(@"B任務"); }); dispatch_async(serialQueue, ^{ sleep(1); NSLog(@"C任務"); }); }

console控制檯顯示以下:

2016-03-15 15:04:11.909 dispatch_queue的多任務GCD使用[92316:2538875] A任務
2016-03-15 15:04:13.910 dispatch_queue的多任務GCD使用[92316:2538875] B任務
2016-03-15 15:04:14.910 dispatch_queue的多任務GCD使用[92316:2538875] C任務

 能夠看獲得,即便須要等待幾秒,後面所添加的任務也必須等待前面的任務完成後才能執行,相似咱們前面所講"飯堂"排隊的例子,隊列徹底按照"先進先出"的順序,也便是所執行的順序取決於:開發者將工做任務添加進隊列的順序。

 

2.並行(concurrent)的執行:可同一時間能夠同時執行多個任務。

  • 負荷:併發執行任務與系統有關,可以同時執行任務的數量是由系統根據應用和此時的系統狀態等動態變化決定的。
  • 順序:因爲並行隊列也是隊列(這是廢話T^T),所以每一個任務的啓動時間也是按照FIFO次序,也就是加入queue的順序,可是結束的順序則依賴各自的任務所須要消耗的時間。

因此,與串行的不一樣的是,雖然啓動時間一致,可是這是「併發執行」,所以不須要等到上一個任務完成後才進行下一個任務。因此每一個塊中的各部分的前後執行的順序須要視狀況而定。

上代碼,找不一樣。。。

- (IBAction)concurrentQueue:(UIButton *)sender {
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(concurrentQueue, ^{
        sleep(3);
        NSLog(@"A任務");
    });
    dispatch_async(concurrentQueue, ^{
        sleep(2);
        NSLog(@"B任務");
    });
    dispatch_async(concurrentQueue, ^{
        sleep(1);
        NSLog(@"C任務");
    });
}

 

 console控制檯顯示以下:

2016-03-15 15:02:06.911 dispatch_queue的多任務GCD使用[92294:2537296] C任務
2016-03-15 15:02:07.907 dispatch_queue的多任務GCD使用[92294:2537147] B任務 2016-03-15 15:02:08.908 dispatch_queue的多任務GCD使用[92294:2537177] A任務

經過控制檯左邊的時間記錄,能夠看到,與串行隊列不一樣的是,並行隊列中這3個任務的並行啓用,與串行不一樣的是,不須要等到A任務調用完,就已經在調用B、C,顯著地提升了線程的執行速度,凸顯了並行隊列所執行的異步操做的並行特性;

另外,從這段代碼中,不一樣的是串行隊列須要建立一個新的隊列,而並行隊列中,只須要調用iOS系統中爲咱們提供的全局共享dispatch_get_global_queue就能夠了:

dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 

第一個參數爲iOS系統爲全局共享隊列提供4種調度的方式,主要區別便是優先級的不一樣而已:

  1. DISPATCH_QUEUE_PRIORITY_HIGH
  2. DISPATCH_QUEUE_PRIORITY_DEFAULT
  3. DISPATCH_QUEUE_PRIORITY_LOW
  4. DISPATCH_QUEUE_PRIORITY_BACKGROUND

咱們採用默認的DISPATCH_QUEUE_PRIORITY_DEFAULT方式,而右邊的第二個參數是蘋果預留的,暫時沒有其餘的含義,因此,通常默認爲:0。

併發的好處就是不須要像串行同樣按照順序執行,併發執行能夠顯著地提升速度。 

 

3、dispatch_group_async的使用

 有時候,咱們會遇到這樣的狀況,UI界面部分的顯示,須要在完成幾個任務再進行主任務,例如3張圖片下載完畢,才通知UI界面已經完成任務。

咱們能夠經過分派組(dispatch group)進行併發程序塊分配的運用,將異步分派(dispatch_async)的全部程序塊設置爲鬆散,或者分配給多個線程來執行,監聽到這組任務所有完成後,使用dispatch_group_notify()通知並調用notify中的塊,例如UI界面的程序塊。

代碼:

- (IBAction)groupQueue:(UIButton *)sender { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ sleep(3); NSLog(@"A任務"); }); dispatch_group_async(group, queue, ^{ sleep(2); NSLog(@"B任務"); }); //group組中的任務完後,通知並調用notify中的塊
    dispatch_group_notify(group, queue, ^{ NSLog(@"主任務"); }); } 

console控制檯顯示以下:

2016-03-16 11:18:41.306 dispatch_queue的多任務GCD使用[94865:2718342] B任務
2016-03-16 11:18:42.302 dispatch_queue的多任務GCD使用[94865:2718341] A任務
2016-03-16 11:18:42.303 dispatch_queue的多任務GCD使用[94865:2718341] 主任務

 結果驗證了前面說的,直到分派組任務都完後,notify添加的任務塊纔會執行。

眼尖的讀者可能也發現,整個任務組完成的時間比2個任務分別運行的時間還要短!這得益於咱們同時進行了兩種計算~

固然在真實的開發運用中,這種明顯運行時間縮短的效果,取決於所須要執行的工做量和可用的資源,以及多個CPU核心的可用性,所以在多核技術日益完善的大環境下,這樣一種多線程技術將獲得更有效的利用。

 

4、dispatch_barrier_async的使用

 dispatch_barrier(分派屏障)是當前面的任務執行完後,才執行barrier塊的任務,並且後面的任務也得等到barrier塊的執行完畢後才能開始執行。

很好地突顯了「障礙物」這樣的特性,那麼代碼上應該怎麼寫呢?

按照併發的性質,咱們在barrierQueue方法中敲入如下代碼:

- (IBAction)barrierQueue:(UIButton *)sender { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ sleep(2); NSLog(@"A任務"); }); dispatch_async(queue, ^{ sleep(1); NSLog(@"B任務"); }); dispatch_barrier_async(queue, ^{ NSLog(@"barrier任務"); }); dispatch_async(queue, ^{ sleep(1); NSLog(@"C任務"); }); }

console控制檯顯示以下:

2016-03-16 13:18:47.525 dispatch_queue的多任務GCD使用[95191:2752854] barrier任務
2016-03-16 13:18:48.529 dispatch_queue的多任務GCD使用[95191:2752839] B任務
2016-03-16 13:18:48.529 dispatch_queue的多任務GCD使用[95191:2752844] C任務
2016-03-16 13:18:49.528 dispatch_queue的多任務GCD使用[95191:2752840] A任務

任務的執行順序依然是跟並行隊列的方法同樣,barrier沒有發揮它的「障礙物」的界限做用。這是由於barrier這一塊是依賴隊列queue的模型來執行的,當隊列爲全局共享時,barrier就沒法發揮其做用。咱們須要新建立一個隊列,

dispatch_queue_t queue = dispatch_queue_create("zuoA", DISPATCH_QUEUE_SERIAL);

 

完整的程序以下:

- (IBAction)barrierQueue:(UIButton *)sender { dispatch_queue_t queue = dispatch_queue_create("zuoA", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ sleep(2); NSLog(@"A任務"); }); dispatch_async(queue, ^{ sleep(1); NSLog(@"B任務"); }); dispatch_barrier_async(queue, ^{ NSLog(@"barrier任務"); }); dispatch_async(queue, ^{ sleep(1); NSLog(@"C任務"); }); }

console控制檯顯示以下:

2016-03-16 13:30:14.251 dispatch_queue的多任務GCD使用[95263:2759658] A任務
2016-03-16 13:30:15.255 dispatch_queue的多任務GCD使用[95263:2759658] B任務
2016-03-16 13:30:15.255 dispatch_queue的多任務GCD使用[95263:2759658] barrier任務
2016-03-16 13:30:16.256 dispatch_queue的多任務GCD使用[95263:2759658] C任務

這就是咱們想要獲得的效果:確實只有在前面A、B任務完成後,barrier任務才能執行,最後才能執行C任務。

 

那麼,dispatch_queue_create爲何要用 DISPATCH_QUEUE_SERIAL,能夠用其餘麼?答案是確定的。把參數換成DISPATCH_QUEUE_CONCURRENT

能夠獲得如下輸出:

2016-03-16 13:34:23.855 dispatch_queue的多任務GCD使用[95294:2762604] B任務 2016-03-16 13:34:24.853 dispatch_queue的多任務GCD使用[95294:2762603] A任務 2016-03-16 13:34:24.853 dispatch_queue的多任務GCD使用[95294:2762603] barrier任務 2016-03-16 13:34:25.856 dispatch_queue的多任務GCD使用[95294:2762603] C任務 

 也就是說,A、B、C任務徹底是按照隊列的順序執行,只是因爲barrier塊的「屏障」做用,把A、B任務放在前面,而使得後來加入的C任務只有等到barrier塊執行完畢才能運行;

 

5、dispatch_suspend(暫停)和 dispatch_resume(繼續)

  • 暫停:當須要暫停某個隊列queue時, 調用dispatch_suspend(queue),此時阻止了queue執行塊對象,且queue的引用計數增長;
  • 繼續:繼續queue時,調用dispatch_resume(queue),此時queue啓動執行塊的操做,queue的引用計數減小;

須要注意的是,suspend與resume是異步的,只在block塊之間調用,並且必須是成對存在的。

 

 

還有一些其餘的dispatch函數,例如

dispatch_once:可使特定的塊在整個應用程序生命週期中只被執行一次~(在單例模式中使用到.)

dispatch_apply:執行某個代碼片斷n次(開發者能夠本身設定)。

dispatch_after:當咱們須要等待幾秒後進行某個操做,可使用這個函數;

 

注意事項:

1.在上面的例子中,咱們沒有使用過手動內管其內存,由於系統會自動管理。

若是你部署的最低目標低於 iOS 6.0 or Mac OS X 10.8

就須要本身管理GCD對象,使用(dispatch_retain,dispatch_release),ARC並不會去管理它們
對於最低sdk版本>=ios6.0來講,GCD對象已經歸入了ARC的管理範圍,咱們就不須要再手工調用 dispatch_release和dispatch_retain函數來增長和減小引用計數以管理內存了。
2.之因此一直在說dispatch函數,而不是方法,方法歸類、對象全部,是由於dispatch是C語言下的函數,函數與方法是不一樣的,函數歸文件全部。
函數與方法有什麼不一樣?看過一篇博文:OC中方法與函數的區別
這個有興趣的童鞋能夠了解一下。
 
本文旨在介紹dispatch_等函數的運用,文字篇幅有些大,敬請見諒(╯▽╰)
相關文章
相關標籤/搜索