在2011的WWDC上,蘋果推出了GCD,從此多線程增加了一種新的方法。GCD要求運行在iOS4.0版本以上或者OS X10.6版本以上。GCD是Grand Central Dispatch的縮寫,是一組用於實現並發編程的C介面。GCD是基於Objective-C的Block的特性開發的,基本的商務邏輯和NSOperation很像。都是添加一個任務到一個隊列,由系統來負責線程的產生和調度。因為直接使用Block,所以使用起來很是方便,降低了多線程開發的門檻。
還是先看一下代碼,和多線程系列(1)裡面同一個例子,用GCD實現如下:
- (void)viewDidLoad{ [super viewDidLoad]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self downloadImage:IMAGE_URL]; });} GCD的調用介面非常簡單,就是將任務提交到Queue裡面。
dispatch_async函數是非同步非阻塞的,調用後會立刻返回,工作由系統線上程池中分配線程去執行。有非同步當然也有同步的,dispatch_sync就是同步的阻塞的API,會一直到添加的任務完成才會返回。
GCD實現多線程確實很簡單,不需要瞭解多線程中的很多細節,而且效率也高。不過disaptch_queue有一些特殊的地方,實際使用中需要瞭解的多一些。dispatch_queue有串列運行和並行運行兩種,顧名思義,串列運行就是任務順序執行,完成一個然後執行下一個,每次只有一個任務在運行;並行運行就是各個任務可以同時運行,同時有多少任務可以並行是根據系統當時的負載決定的,這個開發人員不用關心。
系統提供了3中類型的dispatch queue:
1. main queue
這實際上就是主線程的隊列,所以很明顯,這是一個串列的queue,所有加入main queue的任務都會發動主線程運行,所以加入任務時需要注意不要加入長時間啟動並執行任務。
2. Global queue
我們實際開發中最常用的隊列,是並發隊列。並且有high、default、low三個優先順序(每個優先順序都對應一個獨立的queue)。通過dispatch_get_global_queue這個API可以獲得queue。
3. 自訂queue
dispatch queue是可以自己建立的,通過dispatch_queue_create這個API來建立,dispatch_queue_create(const char *label, dispatch_queue_attr attr)這個API的第一個參數是queue的名字,要求不能重複,所以很多時候和java一樣,推薦用倒寫的網域名稱,第二個參數是建立的queue的類型。這裡要指出,在iOS4.3之前,只能建立串列的queue,參數就是傳遞DISPATCH_QUEUE_SERIAL,iOS4.3之後可以建立並行的queue了,參數是DISPATCH_QUEUE_CONCURRENT。
看到create就會牽涉到記憶體的管理問題,GCD的記憶體管理同樣是用引用計數的方式,不過並不納入iOS的記憶體管理,所以是需要開發人員手動管理的(無論是不是ARC)。
由於有著不同類型的隊列,dispatch_async也可以嵌套使用,還是以同樣的例子,我們也可以這樣寫:
- (void)viewDidLoad{ [super viewDidLoad]; __block UIImage *_image; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:IMAGE_URL]]; _image = [[UIImage alloc] initWithData:data]; dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = _image; }); });} 這樣就在一段代碼裡面實現了所有的功能,包括後台下載,下載之後重新整理UI,而且簡單清晰。
還有一些常用的API介紹如下:
dispatch_get_current_queue()擷取當前隊列
dispatch_queue_get_label()擷取隊列的名字,如果隊列沒有名字,返回NULL
dispatch_set_target_queue()設定給定對象的目標隊列
dispatch_main()會阻塞主線程等待主隊列main queue中的Block執行結束。
有時我們會遇到運行一系列的任務,當任務全部結束後運行另一個特殊的任務這種情境。如果我們用dispatch_sync方法來串列運行所有的任務可以確定啟動並執行先後順序,但效率就會大大降低;但dispatch_async是非同步非阻塞的,所以代碼如下寫是沒用的,不能保證結束所有任務後那個特殊任務的已耗用時間點。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for(id obj in array) dispatch_async(queue, ^{ [self doWork:obj]; }); [self doneWork];
針對這種情況,GCD提供了dispatch group,可以將一組任務集合在一起,等待這組任務完成後再繼續,上面的情境,代碼應該寫成下面的樣子:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); for(id obj in array) dispatch_group_async(group, queue, ^{ [self doWork:obj]; }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); dispatch_release(group); [self doneWork];
方法很簡單,就是將並發的任務用dispatch_group_async非同步添加到一個Group和全域隊列中,dispatch_group_wait會等待這些工作完成後在返回。這樣就實現了任務的順序運行,不過dispatch_group_wait是會阻塞線程的,所以如果是主線程,這個API是不能調用的,那麼我們該怎麼辦呢?
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); for(id obj in array) dispatch_group_async(group, queue, ^{ [self doWork:obj]; }); dispatch_group_notify(group, queue, ^{ [self doneWork]; }); dispatch_release(group); 答案還是很簡單,換一個API,使用dispatch_group_notify這個方法即可。
有的時候我們要同步執行對數組元素的逐個操作,GCD提供了一個簡單的dispatch_apply方法:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply([array count], queue, ^(size_t index){ [self doWork:obj:[array objectAtIndex:index]]; }); [self doneWork];
在使用dispatch_async方法提交並行的任務時,是無法確定任務的執行順序的,但有時我們確實需要某些工作在某個工作完成之後執行,那麼可以使用Dispatch Barrier介面來實現。
dispatch_async(queue, block1); dispatch_async(queue, block2); dispatch_barrier_async(queue, block3); dispatch_async(queue, block4); dispatch_async(queue, block5);
dispatch_barrier_async是非同步,調用後立刻返回。這樣的寫法會保證block1和block2並存執行完成後才會執行block3,完成後再會並行運行block4和block5。
請注意,這裡的queue是一個並行隊列,而且是自訂的那種。
作為蘋果推出的多線程的神器,GCD的內容當然遠遠不止這些。不過通過介紹的最最常用的這些,我們已經可以管中窺豹了。GCD針對各種不同的需求考慮的很全面,並給出了相關的解決方案。開發人員使用GCD應該說是很容易的,所以真正需要關心的就變成了任務怎麼劃分,怎麼運行,是串列還是並行等等。
附上蘋果的Grand Central Dispatch(GCD)Reference文檔,需要深入瞭解的請參考。