iOS--GCD的API的理解與使用

黑白灰的綠i發表於2018-01-26

什麼是GCD?

Grand Central Dispatch(GCD)是非同步執行任務的技術之一。一般將將應用程式中記述的執行緒管理用的程式碼在系統級中實現。開發者只需定義想執行的任務並追加到適當的Dispatch Queue中,GCD就能生成必要的執行緒並計劃執行任務,由於執行緒管理是作為系統的一部分來實現的,因此可統一管理,也可執行任務,這樣就比以前的執行緒更有效率。

Dispatch Queue

含義是,執行處理的等待佇列。 在執行處理時有兩種Dispatch Queue

Serial Dispatch Queue          //  等待現在執行中處理結束

Concurrent Dispatch Queue      //  不等待現在執行中處理結束
複製程式碼

舉例對比:

dispatch_async(queue, blk1);
dispatch_async(queue, blk2);
dispatch_async(queue, blk3);
dispatch_async(queue, blk4);
dispatch_async(queue, blk5);
dispatch_async(queue, blk6);
dispatch_async(queue, blk7);
複製程式碼

當queue為Serial Dispatch Queue 時,因為需要等待現在執行中的處理結果,所以程式會相繼執行1234567。 當queue為Concurrent Dispatch Queue時,因為不需要等待現在執行中的處理結果,所以先執行1,無論1有沒有執行完都會執行2,以此類推,順序是會改變的。此中執行方法開啟的執行緒數量取決於當前系統的狀態。

dispatch_queue_create

通過GCD的API生成Dispatch Queue。

dispatch_queue_t queue=dispatch_queue_create("com.like.same", NULL);
複製程式碼

第一個引數指定Serial Dispatch Queue的名稱,該名稱會出現在應用程式崩潰時所生成的CrashLog中。可以但是不推薦為NULL。 第二個引數指定為NULL。 dispatch_queue_create函式可生成人一多個Dispatch Queue。雖然在一個Serial Dispatch Queue中同時只能執行一個追加處理,但是如果將處理分別追加到四個Serial Dispatch Queue中,各個Serial Dispatch Queue執行一個,即為同時執行四個處理。 通過dispatch_queue_creat函式生成的Dispatch Queue在使用結束後通過dispatch release函式釋放。

dispatch_release(queue);
複製程式碼
多個執行緒更新相同資源導致資料競爭時使用Serial Dispatch Queue。

Main Dispatch Queue/Global Dispatch Queue

獲取系統標準提供的Dispatch Queue。

    //   Main Dispatch Queue的獲取方法
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    //  Global Dispatch Queue高優先順序獲取方法
    dispatch_queue_t highQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    
    //   Global Dispatch Queue預設優先順序獲取方法
    dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //   Global Dispatch Queue低優先順序獲取方法
    dispatch_queue_t lowQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    
    //   Global Dispatch Queue後臺優先順序的獲取方法
    dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
複製程式碼

dispatch_set_target_queue

變更生成的Dispatch Queue的執行優先順序要使用dispatch_set_target_queue函式。 eg:

    dispatch_queue_t myQueue = dispatch_queue_create("com.like.same", NULL);
    
    dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    
    dispatch_set_target_queue(myQueue, backgroundQueue);
複製程式碼

在必須將不可並行執行的處理追加到多個Serial Dispatch Queue中時,如果使用dispatch_set_target_queue 函式將目標指定為某一個Serial Dispatch Queue,即可防止處理並行執行。

dispatch_after

指定時間後執行處理的情況,可使用dispatch_after函式來實現。 eg:

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
    
    dispatch_after(time, dispatch_get_main_queue(), ^{
        
        NSLog(@"666");
        
    });
複製程式碼

第一個引數時指定時間用的dispatch_time_t型別的的值。 第二個引數是指定要追加處理的Dispatch Queue。 dispatch_time函式能夠獲取從第一個引數dispatch_time_t型別值中指定的時間開始,到第二個引數指定的單位時間後的時間。上面的程式碼中第一個引數代表現在。 dispatch_time函式通常用於計算相對時間,而dispatch_walltime函式用語計算絕對時間。 eg:

dispatch_time_t getDispatchTimeByDate(NSDate * date)
{
    NSTimeInterval interval;
    double second, subsecond;
    struct timespec time;
    dispatch_time_t milestone;
    
    interval = [date timeIntervalSince1970];
    subsecond = modf(interval, &second);
    time.tv_sec = second;
    time.tv_nsec = subsecond * NSEC_PER_SEC;
    milestone = dispatch_time(&time, 0);
    
    return milestone;
}
複製程式碼

Dispatch Group

再追加到Dispatch Queue中的多個處理全部請求結束後想要執行結果處理,這種情況下使用Dispatch Group。 eg:

    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, ^{
        NSLog(@"blk0");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"done");
    });
複製程式碼

列印結果為:

blk1
blk0
blk2
done
複製程式碼

由於多個執行緒並行執行,所以執行的順序會發生改變,但是done一定是在最後輸出。

Dispatch Group.png
另外,在Dispatch Group中也可以使用dispatch_group_wait函式僅等待全部處理執行結束。

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
複製程式碼

第二個引數為等待的時間這裡使用的是永久,也就是當處理未結束,程式將一直等待下去,中途不能取消。 如果dispatch_group_wait函式的返回值不為0,就意味著雖然經過了指定的時間,但屬於Dispatch Group的某一個處理還在執行中,如果返回0,那麼全部處理執行結束。 一旦呼叫dispatch_group_wait函式,該函式處於呼叫的狀態而不反回。

dispatch_barrier_async

dispatch_barrier_async函式會等待追加到Concurrent Dispatch Queue上的並行執行的處理全部結束之後,再將指定的處理追加到該Concurrent Dispatch Queue中。然後由dispatch_barrier_async函式處理完畢之後,Concurrent Dispatch Queue再回復為一般動作(繼續並行執行)。 eg:

    dispatch_async(queue, blk0);
    dispatch_async(queue, blk1);
    dispatch_async(queue, blk2);
    dispatch_barrier_async(queue, blk);
    dispatch_async(queue, blk3);
    dispatch_async(queue, blk4);
    dispatch_async(queue, blk5);
複製程式碼

執行結果恆為012在前,blk在中,345在後。

使用Concurrent Dispatch Queue和dispatch_barrier_async函式可實現高效率的資料庫訪問和檔案訪問。

圖例:

dispatch_barrier_async.png

dispatch_sync

dispatch_async中的“async”意味著非同步,就是將指定的Block非同步的追加到指定Dispatch Queue中,dispatch_async函式不做任何等待。 “sync”則意味著同步,就是將指定的Block非同步的追加到指定Dispatch Queue中,在追加的block結束前,dispatch_sync函式會一直等待。 eg:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_sync(queue, ^{
        NSLog(@"上面處理已經結束");
    });
複製程式碼

一旦呼叫dispatch_sync函式,那麼指定的處理執行結束之前,該函式不會返回。可以說是簡易版的dispatch_group_wait。 dispatch_barrier_async函式的作用是在等待追加的處理全部結束之後,再追加處理到Dispatch Queue中,此外,它還與dispatch_sync函式相同,會等待追加處理的執行結果。 但是頻繁使用dispatch_sync容易造成死鎖。 eg:

 //  造成死鎖
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"????");
    });
    
    //   同樣造成死鎖
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"???");
        });
    });
複製程式碼

dispatch_once

dispatch_once函式是保證在應用程式執行中只執行一次指定處理的API。 單例模式:

static Singleton* _instance = nil;
+(instancetype) shareInstance
{
    static dispatch_once_t onceToken ;
    dispatch_once(&onceToken, ^{
    _instance = [[super allocWithZone:NULL] init] ;
}) ;
return _instance ;
}

+(id) allocWithZone:(struct _NSZone *)zone
{
    return [Singleton shareInstance] ;
}

-(id) copyWithZone:(struct _NSZone *)zone
{
    return [Singleton shareInstance] ;
}
複製程式碼

dispatch_apply

該函式是dispatch_sync函式和Dispatch Group的關聯API。按照指定的次數將指定的Block追加到指定的Dispatch Queue中,並等待全部處理執行結束。 eg:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zu",index);
    });
複製程式碼

由於此函式與dispatch_sync函式一樣會等待處理執行結束,所以最好在dispatch_async函式中非同步的執行dispatch_apply函式。

dispatch_suspend/dispatch_resume

dispatch_suspend函式掛起指定的Dispatch Queue。

dispatch_suspend(queue);
複製程式碼

dispatch_resume函式恢復指定的dispatch Queue。

dispatch_resume(queue);
複製程式碼

這兩個函式對已經執行的處理沒有影響。掛起後,追加到Dispatch Queue中但尚未執行的處理在此之後停止執行。而恢復能使這些處理能夠繼續執行。

Dispatch Semaphore

待續。。。。

相關文章