iOS多線程的方法有3種:html
其中,由蘋果所倡導的爲多核的並行運算提出的解決方案: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(異步)與
sync(同步):
固然,咱們也可使用同步任務,使用dispatch_sync
函數添加到相應的隊列中,而這個函數會阻塞當前調用線程,直到相應任務完成執行。
可是,也正由於這樣的同步特性,在實際項目中,當有同步任務添加到正在執行同步任務的隊列時,串行的隊列會出現死鎖。並且因爲同步任務會阻塞主線程的運行,可能會致使某個事件沒法響應。
隊列(queue):
須要注意的是,調用dispatch_async不會讓塊運行,而是把塊添加到隊列末尾。隊列不是線程,它的做用是組織塊。(若是讀者學過數據結構的知識,就會知道隊列的基本特徵如飯堂排隊隊,先到的排前面,先打到飯,也就是「先進先出」原理)
在GCD中,能夠給開發者調用的常見公共隊列有如下兩種:
dispatch_get_global_queue
:用於獲取應用全局共享的併發隊列 (提供多個線程來執行任務,因此能夠按序啓動多個任務併發執行。可用於後臺執行任務)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)的執行:可同一時間能夠同時執行多個任務。
因此,與串行的不一樣的是,雖然啓動時間一致,可是這是「併發執行」,所以不須要等到上一個任務完成後才進行下一個任務。因此每一個塊中的各部分的前後執行的順序須要視狀況而定。
上代碼,找不一樣。。。
- (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種調度的方式,主要區別便是優先級的不一樣而已:
咱們採用默認的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_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