iOS多線程系列(3) – iPhone手機開發技術文章 iPhone軟體開發教學課程

在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文檔,需要深入瞭解的請參考。

發佈留言