【iOS】玩轉 - GCD

尚妝產品技術刊讀發表於2017-03-09

本文來自尚妝iOS團隊嘉文
發表於尚妝github部落格,歡迎訂閱!

GCD介紹


Grand Central Dispatch (GCD)是Apple開發的一個多核程式設計的解決方法
基於C語言,提供了非常多強大的函式

術語


同步 (Synchronous)

在當前執行緒中執行任務,不具備開啟新執行緒的能力
提交的任務在執行完成後才會返回
同步函式: dispatch_sync()

非同步 (Asynchronous)

在新執行緒中執行任務,具備開啟新執行緒的能力
提交的任務立刻返回,在後臺佇列中執行
非同步函式: dispatch_async()

序列 (Serial)

一個任務執行完畢後,再執行下一個任務

併發 (Concurrent)

多個任務同時執行(自動開啟多個執行緒),只有在非同步函式下才有效

描述 說明
queue 佇列
main 主佇列
global 全域性佇列
dispatch_queue_t 描述佇列
dispatch_block_t 描述任務
dispatch_once_t 描述一次性
dispatch_time_t 描述時間
dispatch_group_t 描述佇列組
dispatch_semaphore_t 描述訊號量
函式 說明
dispatch_sync() 同步執行
dispatch_async() 非同步執行
dispatch_after() 延時執行
dispatch_once() 一次性執行
dispatch_apply() 提交佇列
dispatch_queue_create() 建立佇列
dispatch_group_create() 建立佇列組
dispatch_group_async() 提交任務到佇列組
dispatch_group_enter() / dispatch_group_leave() 將佇列組中的任務未執行完畢的任務數目加減1(兩個函式要配合使用)
dispatch_group_notify() 監聽佇列組執行完畢
dispatch_group_wait() 設定等待時間(返回 0成功,1失敗)
注意:

1.所有的執行都放到佇列中(queue),佇列的特點是FIFO(先提交的先執行)
2.必須在主執行緒訪問 UIKit 的類
3.併發佇列只在非同步函式下才有效

#基本使用

 NSLog(@"當前執行緒: %@", [NSThread currentThread]);
 //獲取主佇列
 dispatch_queue_t mainQueue = dispatch_get_main_queue();

 //獲取全域性併發佇列
 dispatch_queue_t otherQueue =   dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

 //同步函式(在當前執行緒中執行,不具備開啟新執行緒的能力)
 dispatch_sync(otherQueue, ^{
      NSLog(@"同步 %@", [NSThread currentThread]);
 });

 //非同步函式(在另一條執行緒中執行,具備開啟新執行緒的能力)
 dispatch_async(otherQueue, ^{
      NSLog(@"非同步 %@", [NSThread currentThread]);
 });

 //輸出:   當前執行緒: {number = 1, name = main}
 //輸出:   同步 {number = 1, name = main}
 //輸出:   非同步 {number = 3, name = (null)}複製程式碼
延時執行 dispatch_after()

dispatch_after()延遲一段時間把一項任務提交到佇列中執行,返回之後就不能取消
常用來在在主佇列上延遲執行一項任務

    NSLog(@"當前執行緒 %@", [NSThread currentThread]);

    //GCD延時呼叫(主執行緒)(主佇列)
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"GCD延時(主執行緒) %@", [NSThread currentThread]);
    });

    //GCD延時呼叫(其他執行緒)(全域性併發佇列)
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"GCD延時(其他執行緒) %@", [NSThread currentThread]);
    });

    //輸出:   當前執行緒 {number = 1, name = main}
    //輸出:   GCD延時(主執行緒) {number = 1, name = main}
    //輸出:   GCD延時(其他執行緒) {number = 3, name = (null)}複製程式碼
一次性執行 dispatch_once()

整個程式執行中,只會執行一次 (預設執行緒是安全的)
dispatch_once() 以執行緒安全的方式執行且僅執行其程式碼塊一次

    for (NSInteger i = 0; i < 10; i++) {

        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"GCD一次性執行(預設執行緒是安全的)");
        });

    }

 //輸出:   GCD一次性執行(預設執行緒是安全的)複製程式碼
//使用GCD初始化單例
+ (instancetype)sharedManager { 

    static PhotoManager *sharedPhotoManager = nil; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
        sharedPhotoManager = [[PhotoManager alloc] init]; 
    }); 

    return sharedPhotoManager; 
}複製程式碼
提交 dispatch_apply()

把一項任務提交到佇列中多次執行,具體是並行執行還是序列執行由佇列本身決定
dispatch_apply不會立刻返回,在執行完畢後才會返回,是同步的呼叫。

佇列

任務1,任務2依次執行,所有任務都執行成功後回到主執行緒
(效率不高)

 NSLog(@"當前執行緒 %@", [NSThread currentThread]);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //全域性併發佇列
        for (NSInteger i = 0; i < 5; i++) {
            NSLog(@"任務1 %@", [NSThread currentThread]);
        }

        for (NSInteger i = 0; i < 5; i++) {
            NSLog(@"任務2 %@", [NSThread currentThread]);
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            //主佇列
            NSLog(@"主執行緒執行(重新整理UI) %@", [NSThread currentThread]);
        });

    });

    //輸出:   當前執行緒 {number = 1, name = main}
    //輸出:   任務1 {number = 3, name = (null)}
    //輸出:   任務1 {number = 3, name = (null)}
    //輸出:   任務1 {number = 3, name = (null)}
    //輸出:   任務1 {number = 3, name = (null)}
    //輸出:   任務1 {number = 3, name = (null)}
    //輸出:   任務2 {number = 3, name = (null)}
    //輸出:   任務2 {number = 3, name = (null)}
    //輸出:   任務2 {number = 3, name = (null)}
    //輸出:   任務2 {number = 3, name = (null)}
    //輸出:   任務2 {number = 3, name = (null)}
    //輸出:   任務2 {number = 3, name = (null)}
    //輸出:   主執行緒(重新整理UI) {number = 1, name = main}複製程式碼
佇列組

任務1,任務2同時執行,所有任務都執行成功後回到主執行緒
(效率高)

    NSLog(@"當前執行緒 %@", [NSThread currentThread]);

    //(1)建立一個佇列組
    dispatch_group_t group= dispatch_group_create();

    //(2)開啟任務1
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        for (NSInteger i = 0; i < 5; i++) {
            NSLog(@"任務1 %@", [NSThread currentThread]);
        }

    });

    //(3)開啟任務2
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        for (NSInteger i = 0; i < 5; i++) {
            NSLog(@"任務2 %@", [NSThread currentThread]);
        }

    });

    //(4)所有任務執行完畢,回到主執行緒
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{

        NSLog(@"主執行緒(重新整理UI) %@", [NSThread currentThread]);

    });

    //輸出:   當前執行緒 {number = 1, name = main}
    //輸出:   任務1 {number = 3, name = (null)}
    //輸出:   任務1 {number = 3, name = (null)}
    //輸出:   任務1 {number = 3, name = (null)}
    //輸出:   任務2 {number = 4, name = (null)}
    //輸出:   任務2 {number = 4, name = (null)}
    //輸出:   任務1 {number = 3, name = (null)}
    //輸出:   任務2 {number = 4, name = (null)}
    //輸出:   任務1 {number = 3, name = (null)}
    //輸出:   任務2 {number = 4, name = (null)}
    //輸出:   任務2 {number = 4, name = (null)}
    //輸出:   主執行緒(重新整理UI) {number = 1, name = main}複製程式碼

#序列與併發

【iOS】玩轉 - GCD
各個佇列的執行效果

序列佇列

【iOS】玩轉 - GCD
序列佇列

一個任務執行完畢後,再執行下一個任務
主佇列是GCD自帶的一種特殊的序列佇列,放在主佇列中的任務,都會放到主執行緒中執行

    //(1)使用dispatch_queue_create函式建立序列佇列
    //引數1: 佇列名稱
 //引數2: 佇列屬性 (一般用NULL)
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", NULL);

    //(2)使用主佇列(跟主執行緒相關聯的佇列)
    dispatch_queue_t serialMainQueue = dispatch_get_main_queue();複製程式碼
併發佇列

【iOS】玩轉 - GCD
併發佇列

多個任務併發執行(自動開啟多個執行緒同時執行任務)

併發功能只有在非同步(dispatch_async)函式下才有效!!!

GCD預設已經提供了全域性的併發佇列,供整個應用使用,不需要手動建立

併發佇列優先順序 快捷值 優先順序
DISPATCH_QUEUE_PRIORITY_HIGH 2
DISPATCH_QUEUE_PRIORITY_DEFAULT 0 中(預設)
DISPATCH_QUEUE_PRIORITY_LOW (-2)
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 後臺
 //(1)使用dispatch_get_global_queue函式獲得全域性的併發佇列
    //引數1: 優先順序
 //引數2: 暫時無用引數 (傳0)
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);複製程式碼
非同步函式_併發佇列

(開啟新執行緒,併發執行任務)

    NSLog(@"當前執行緒 %@", [NSThread currentThread]);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"任務1 %@", [NSThread currentThread]);
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"任務2 %@", [NSThread currentThread]);
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"任務3 %@", [NSThread currentThread]);
    });

    //輸出:   當前執行緒 {number = 1, name = main}
    //輸出:   任務1 {number = 3, name = (null)}
    //輸出:   任務3 {number = 4, name = (null)}
    //輸出:   任務2 {number = 5, name = (null)}複製程式碼
非同步函式_序列佇列

(開啟新執行緒,序列執行任務)

    NSLog(@"當前執行緒 %@", [NSThread currentThread]);
    //建立序列佇列
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", NULL);

    dispatch_async(serialQueue, ^{
        NSLog(@"任務1 %@", [NSThread currentThread]);
    });

    dispatch_async(serialQueue, ^{
        NSLog(@"任務2 %@", [NSThread currentThread]);
    });

    dispatch_async(serialQueue, ^{
        NSLog(@"任務3 %@", [NSThread currentThread]);
    });

    //輸出:   當前執行緒 {number = 1, name = main}
    //輸出:   任務1 {number = 3, name = (null)}
    //輸出:   任務2 {number = 3, name = (null)}
    //輸出:   任務3 {number = 3, name = (null)}複製程式碼
同步函式_併發佇列

(不會開啟新執行緒,併發執行任務失效!)

    NSLog(@"當前執行緒 %@", [NSThread currentThread]);
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任務1 %@", [NSThread currentThread]);
    });

    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任務2 %@", [NSThread currentThread]);
    });

    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任務3 %@", [NSThread currentThread]);
    });

    //輸出:   當前執行緒 {number = 1, name = main}
    //輸出:   任務1 {number = 1, name = main}
    //輸出:   任務2 {number = 1, name = main}
    //輸出:   任務3 {number = 1, name = main}複製程式碼
同步函式_序列佇列

(不會開啟新執行緒,序列執行任務)

 NSLog(@"當前執行緒 %@", [NSThread currentThread]);
 //建立序列佇列
 dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", NULL);
 dispatch_sync(serialQueue, ^{
     NSLog(@"任務1 %@", [NSThread currentThread]);
 });

 dispatch_sync(serialQueue, ^{
      NSLog(@"任務2 %@", [NSThread currentThread]);
 });

 dispatch_sync(serialQueue, ^{
     NSLog(@"任務3 %@", [NSThread currentThread]);
 });

 //輸出:   當前執行緒 {number = 1, name = main}
 //輸出:   任務1 {number = 1, name = main}
 //輸出:   任務2 {number = 1, name = main}
 //輸出:   任務3 {number = 1, name = main}複製程式碼

相關文章