1. 程式人生 > >iOS多執行緒程式設計(四)------ GCD(Grand Central Dispatch)

iOS多執行緒程式設計(四)------ GCD(Grand Central Dispatch)

一、簡介
是基於C語言開發的一套多執行緒開發機制,也是目前蘋果官方推薦的多執行緒開發方法,用起來也最簡單,只是它基於C語言開發,並不像NSOperation是面向物件的開發,而是完全面向過程的。如果使用GCD,完全由系統管理執行緒,我們不需要編寫執行緒程式碼。只需定義想要執行的任務,然後新增到適當的排程佇列(dispatch_queue).GCD會負責建立執行緒和排程你的任務,系統會直接提供執行緒管理。

二、任務和佇列
GCD中有兩個核心概念
(1)任務:執行什麼操作
(2)佇列:用來存放任務
GCD的使用就兩個步驟
(1)定製任務
(2)確定想做的事情
將任務新增到佇列中,GCD會自動將佇列中的任務取出,放到對應的執行緒中執行
提示:任務的取出遵循佇列的FIFO原則:先進先出,後進後出

三、執行任務
1、GCD中有2個用來執行任務的函式
說明:把右邊的引數(任務)提交給左邊的引數(佇列)進行執行
(1)用同步的方式執行任務 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
引數說明:queue:佇列; block:任務
(2)用非同步的方式執行任務 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

2、同步和非同步的區別
同步:在當前執行緒中執行
非同步:在另一條執行緒中執行

四、佇列
1、GCD的佇列可以分為2大型別
(1)併發佇列(Concurrent Dispatch Queue):可以讓多個任務併發(同時)執行(自動開啟多個執行緒同時執行任務)併發功能只有在非同步(dispatch_async)函式才有效

(2)序列佇列(Serial Dispatch Queue):讓任務一個接著一個地執行(一個任務執行完畢後,再執行下一個任務)

2、補充說明
有4個術語比較容易混淆:同步、非同步、併發、序列(在部落格 多執行緒程式設計(-)—-概念 也提到了)
同步和非同步決定了要不要開啟新的執行緒
同步:在當前執行緒中執行任務,不具備開啟新執行緒的能力
非同步:在新的執行緒中執行任務,具備開啟新執行緒的能力
併發和序列決定了任務的執行方式
併發:多個任務併發執行
序列:一個任務執行完畢後,再執行下一個任務

五、(同步/非同步)序列佇列和(同步/非同步)併發佇列開啟執行緒的總結 (程式碼示例)
說明:
(1)同步函式不具備開啟執行緒的能力,無論是什麼佇列都不會開啟執行緒;非同步函式具備開啟執行緒的能力,開啟幾條執行緒有佇列決定(序列佇列只會開啟一條新的執行緒,併發佇列會開啟多條執行緒)
(2) MRC下凡是函式中,各種函式名中帶有create\copy\new\retain等字眼,都需要在不需要使用這個資料的時候進行release
ARC下GCD的資料型別不需要再作release
CF(core Foundation)的資料在ARC環境下還是需要release
(3) 非同步函式具備開執行緒的能力,但不一定會開執行緒

1、非同步併發佇列(同時開啟N個執行緒)
這裡寫圖片描述

/**
 *  非同步併發佇列
 */
- (void)asynchronousConcurrent{
    NSLog(@"非同步函式往併發佇列中新增任務");
    NSLog(@"主執行緒1111 ---- %@", [NSThread currentThread]);

    // 1、建立併發佇列
    // 方法一 和建立序列佇列一樣
    //    dispatch_queue_t queue = dispatch_queue_create("asynConcurrent", DISPATCH_QUEUE_CONCURRENT);
    // 方法二 獲取全域性併發佇列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 2、新增任務到佇列
    dispatch_async(queue, ^{
        NSLog(@"下載圖片1 ------ %@", [NSThread currentThread]);
        [self loadImage:1];
    });

    dispatch_async(queue, ^{
        NSLog(@"下載圖片2------ %@", [NSThread currentThread]);
        [self loadImage:2];
    });

    dispatch_async(queue, ^{
        NSLog(@"下載圖片3 ------ %@", [NSThread currentThread]);
        [self loadImage:3];
    });

    NSLog(@"主執行緒2222 ---- %@", [NSThread currentThread]);


    // 列印結果 開啟多個執行緒,併發執行。沒有先後順序(有的話也是剛好巧合而已) 可以看number 可以看做執行緒值
    /**
     第一次執行
     2016-08-24 12:53:24.026 YJGCDDemo[1220:24092] 非同步函式往併發佇列中新增任務
     2016-08-24 12:53:24.027 YJGCDDemo[1220:24092] 主執行緒1111 ---- <NSThread: 0x7f81bae04b90>{number = 1, name = main}
     2016-08-24 12:53:24.027 YJGCDDemo[1220:24092] 主執行緒2222 ---- <NSThread: 0x7f81bae04b90>{number = 1, name = main}
     2016-08-24 12:53:24.027 YJGCDDemo[1220:24126] 下載圖片1 ------ <NSThread: 0x7f81baf25e90>{number = 2, name = (null)}
     2016-08-24 12:53:24.027 YJGCDDemo[1220:24128] 下載圖片2------ <NSThread: 0x7f81bacc5fe0>{number = 3, name = (null)}
     2016-08-24 12:53:24.027 YJGCDDemo[1220:24127] 下載圖片3 ------ <NSThread: 0x7f81baf81ac0>{number = 4, name = (null)}

     第二次執行
     2016-08-24 12:53:32.427 YJGCDDemo[1220:24092] 非同步函式往併發佇列中新增任務
     2016-08-24 12:53:32.427 YJGCDDemo[1220:24092] 主執行緒1111 ---- <NSThread: 0x7f81bae04b90>{number = 1, name = main}
     2016-08-24 12:53:32.427 YJGCDDemo[1220:24092] 主執行緒2222 ---- <NSThread: 0x7f81bae04b90>{number = 1, name = main}
     2016-08-24 12:53:32.427 YJGCDDemo[1220:24126] 下載圖片2------ <NSThread: 0x7f81baf25e90>{number = 2, name = (null)}
     2016-08-24 12:53:32.427 YJGCDDemo[1220:24260] 下載圖片3 ------ <NSThread: 0x7f81baf7f660>{number = 7, name = (null)}
     2016-08-24 12:53:32.427 YJGCDDemo[1220:24153] 下載圖片1 ------ <NSThread: 0x7f81baf81ac0>{number = 6, name = (null)}

     */
}

2、非同步序列佇列(會開啟執行緒,但是隻開啟一個執行緒)
這裡寫圖片描述

/**
 *  非同步序列佇列
 */
- (void)asynchronousSerial{

    NSLog(@"用非同步函式往序列佇列中新增任務");
    NSLog(@"主執行緒1111 ---- %@", [NSThread currentThread]);

    //1. 建立序列佇列
    dispatch_queue_t queue = dispatch_queue_create("asynSerial", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"下載圖片1 --- %@", [NSThread currentThread]);
        [self loadImage:1];
    });


    dispatch_async(queue, ^{
        NSLog(@"下載圖片2 --- %@", [NSThread currentThread]);
        [self loadImage:2];
    });

    dispatch_async(queue, ^{
        NSLog(@"下載圖片3 --- %@", [NSThread currentThread]);
        [self loadImage:3];
    });

    NSLog(@"主執行緒2222 ---- %@", [NSThread currentThread]);


    // 列印結果:非同步序列佇列,會開啟一個執行緒,順序執行。 看執行結果也可以看出,圖片是一張下載完在下載下一張的。
    /**
     2016-08-24 12:39:14.195 YJGCDDemo[942:16507] 用非同步函式往序列佇列中新增任務
     2016-08-24 12:39:14.196 YJGCDDemo[942:16507] 主執行緒1111 ---- <NSThread: 0x7f8ed1f05f90>{number = 1, name = main}
     2016-08-24 12:39:14.196 YJGCDDemo[942:16507] 主執行緒2222 ---- <NSThread: 0x7f8ed1f05f90>{number = 1, name = main}
     2016-08-24 12:39:14.196 YJGCDDemo[942:16622] 下載圖片1 --- <NSThread: 0x7f8ed1c43c00>{number = 2, name = (null)}
     2016-08-24 12:39:14.261 YJGCDDemo[942:16622] 下載圖片2 --- <NSThread: 0x7f8ed1c43c00>{number = 2, name = (null)}
     2016-08-24 12:39:14.303 YJGCDDemo[942:16622] 下載圖片3 --- <NSThread: 0x7f8ed1c43c00>{number = 2, name = (null)}

     */
}

3、同步併發佇列(不會開啟新的執行緒,併發佇列失去併發的功能)
這裡寫圖片描述

/**
 *  同步併發佇列
 */
- (void)synchronousConcurrent{
        NSLog(@"用同步函式往併發佇列中新增任務");
        NSLog(@"主執行緒1111 ---- %@", [NSThread currentThread]);

        // 1.建立併發佇列
        // 方式一 一般都使用這種方式獲取
    //    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        // 方式二 和建立序列佇列一樣
        dispatch_queue_t queue = dispatch_queue_create("syncConcurrentncy", DISPATCH_QUEUE_CONCURRENT);

        // 2.加添任務到佇列
        dispatch_sync(queue, ^{
            NSLog(@"下載圖片1 ---- %@", [NSThread currentThread]);
            [self loadImage:1];
        });

        dispatch_sync(queue, ^{
            NSLog(@"下載圖片2 ---- %@", [NSThread currentThread]);
            [self loadImage:2];
        });

        dispatch_sync(queue, ^{
            NSLog(@"下載圖片3 ---- %@", [NSThread currentThread]);
            [self loadImage:3];
        });

        NSLog(@"主執行緒2222 ---- %@", [NSThread currentThread]);


    // 列印結果 和同步序列佇列一樣 這邊併發佇列效果失效,不會開啟執行緒。
    /**
     2016-08-24 11:20:44.153 YJGCDDemo[43913:3002870] 用同步函式往併發佇列中新增任務
     2016-08-24 11:20:44.154 YJGCDDemo[43913:3002870] 主執行緒1111 ---- <NSThread: 0x7f8742604eb0>{number = 1, name = main}
     2016-08-24 11:20:44.154 YJGCDDemo[43913:3002870] 下載圖片1 ---- <NSThread: 0x7f8742604eb0>{number = 1, name = main}
     2016-08-24 11:20:44.433 YJGCDDemo[43913:3002870] 下載圖片2 ---- <NSThread: 0x7f8742604eb0>{number = 1, name = main}
     2016-08-24 11:20:44.475 YJGCDDemo[43913:3002870] 下載圖片3 ---- <NSThread: 0x7f8742604eb0>{number = 1, name = main}
     2016-08-24 11:20:44.508 YJGCDDemo[43913:3002870] 主執行緒2222 ---- <NSThread: 0x7f8742604eb0>{number = 1, name = main}

     */
}

4、同步序列佇列(不會開啟新的執行緒)
這裡寫圖片描述

/**
 *  同步序列佇列
 */
- (void)synchronousSerial{
    NSLog(@"用同步函式往序列佇列中新增任務");
    NSLog(@"主執行緒111----- %@", [NSThread currentThread]);

    // 1、建立序列佇列 // DISPATCH_QUEUE_SERIAL 序列佇列 也可以為NULL NULL時 預設是 序列佇列; DISPATCH_QUEUE_CONCURRENT 併發佇列
    dispatch_queue_t queue = dispatch_queue_create("syncSerial", DISPATCH_QUEUE_SERIAL);

    // 2、新增任務到佇列中執行
    dispatch_sync(queue, ^{
        // 此塊程式碼沒有任何意義,只是為了體現同步序列佇列的效果, 只能執行完了 才能執行面下的,
        for (int i = 0; i < 30000; i++) {
            //
            NSLog(@"%i", i);
        }
        NSLog(@"下載圖片1 ---- %@", [NSThread currentThread]);
        [self loadImage:1];
    });

    dispatch_sync(queue, ^{
        for (int i = 0; i < 30000; i++) {
            //
            NSLog(@"%i", i);
        }
        NSLog(@"下載圖片2 -- %@", [NSThread currentThread]);
        [self loadImage:2];
    });

    dispatch_sync(queue, ^{
        for (int i = 0; i < 30000; i++) {
            //
            NSLog(@"%i", i);
        }
        NSLog(@"下載圖片3 -- %@", [NSThread currentThread]);
        [self loadImage:3];
    });

    NSLog(@"主執行緒222----- %@", [NSThread currentThread]);


    // 列印結果就是同步按順序執行。 每個任務按順序執行,不開啟執行緒
    /**
     2016-08-24 10:52:55.049 YJGCDDemo[43413:2986818] 用同步函式往序列佇列中新增任務
     2016-08-24 10:52:55.049 YJGCDDemo[43413:2986818] 主執行緒111----- <NSThread: 0x7fd5dbc00dd0>{number = 1, name = main}
     2016-08-24 10:52:55.050 YJGCDDemo[43413:2986818] 下載圖片1 ---- <NSThread: 0x7fd5dbc00dd0>{number = 1, name = main}
     2016-08-24 10:52:55.580 YJGCDDemo[43413:2986818] 下載圖片2 -- <NSThread: 0x7fd5dbc00dd0>{number = 1, name = main}
     2016-08-24 10:52:55.616 YJGCDDemo[43413:2986818] 下載圖片3 -- <NSThread: 0x7fd5dbc00dd0>{number = 1, name = main}
     2016-08-24 10:52:55.644 YJGCDDemo[43413:2986818] 主執行緒222----- <NSThread: 0x7fd5dbc00dd0>{number = 1, name = main}

     */
}

六、常用方法 在Demo中的CommonMethodsViewCotroller類

1、後臺執行

 dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // something
    });

2、主執行緒執行

dispatch_async(dispatch_get_main_queue(), ^{
        // something
    });

3、一次性執行 dispatch_once()

static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // code to be execution once
        NSLog(@"改行程式碼只執行一次");
    });

4、延遲N秒執行 (這邊列舉了四種方式) dispatch_time()
這裡寫圖片描述

    NSLog(@"列印執行緒----- %@", [NSThread currentThread]);
    // 延時執行方式一 使用NSObject的方法
    // 2秒後再呼叫self的run方法
//    [self performSelector:@selector(loadImage) withObject:nil afterDelay:2.0];

    // 延遲執行方式二 使用GCD函式
       // 在同步函式中執行
        // 注意 如果使用非同步函式 dispatch_async 那麼[self performSelector:@selector(loadImage) withObject:nil afterDelay:5.0]; 不會被執行
//    dispatch_queue_t queue = dispatch_queue_create("yangjian.net.cn", 0);
//    dispatch_sync(queue, ^{
//        [self performSelector:@selector(loadImage) withObject:nil afterDelay:2.0];
//    });

    // 延遲執行方式三 可以安排其執行緒---> 主佇列
//    dispatch_queue_t queue = dispatch_get_main_queue();
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
//        NSLog(@"主佇列--延遲執行------%@",[NSThread currentThread]);
//        [self gcdLoadImage];
//    });

    // 延遲執行方式四 可以安排其執行緒---> 併發佇列
    //1、獲取全域性併發佇列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //2、計算任務執行的時間
    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
    //3、會在when這個時間點,執行queue中的這個任務
    dispatch_after(when, queue, ^{
        NSLog(@"併發佇列--延遲執行 ---- %@", [NSThread currentThread]);
        [self gcdLoadImage];
    });

5、執行某個程式碼片段N次 dispatch_apply()

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(4, globalQueue, ^(size_t index) {
        // 執行4次
    });

6、佇列組的使用 dispatch_group_async()

    // 需求
    //1  分別非同步執行2個耗時的操作
    //2  等兩個非同步操作都執行完畢後,再回到主執行緒執行操作

//   如果想要快速高效地實現上述需求,可以考慮用佇列組

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        // 併發執行的執行緒一
    });

    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        // 併發執行的執行緒二
    });

    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        // 等前面的非同步操作都執行完畢後,回到主執行緒
    });

6、dispatch_barrier_async()

/**
 *   使用此方法建立的任務首先會檢視佇列中有沒有別的任務要執行,如果有,則會等待已有任務執行完畢再執行;同時在此方法後新增的任務必須等待此方法中任務執行後才能執行。(利用這個方法可以控制執行順序,例如前面先載入最後一張圖片的需求就可以先使用這個方法將最後一張圖片載入的操作新增到佇列,然後呼叫dispatch_async()新增其他圖片載入任務)
 */
- (void)barrier{
//    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t queue = dispatch_queue_create("RICHARD", DISPATCH_QUEUE_CONCURRENT); // 建立併發佇列

    dispatch_async(queue, ^{
        NSLog(@"下載圖片1 --- %@", [NSThread currentThread]);
        UIImage *image = [self requestImageData:@"http://atth.eduu.com/album/201203/12/1475134_1331559643qMzc.jpg"];

        // 回到主執行緒
        dispatch_queue_t mainQueue1 = dispatch_get_main_queue();
        dispatch_async(mainQueue1, ^{
            self.imageViewOne.image = image;
        });
    });

    dispatch_async(queue, ^{
        NSLog(@"下載圖片2 --- %@", [NSThread currentThread]);
    });

    dispatch_barrier_async(queue, ^{
        NSLog(@"dispatch_barrier_async 下載圖片3  --- %@", [NSThread currentThread]);
        UIImage *image = [self requestImageData:@"http://5.26923.com/download/pic/000/335/06efd7b7d40328f1470d4fd99a214243.jpg"];
        dispatch_async(main_queue, ^{
            self.imageViewThree.image = image;
        });
    });



    dispatch_async(queue, ^{
        NSLog(@"下載圖片4 --- %@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"下載圖片5 --- %@", [NSThread currentThread]);
        UIImage *image = [self requestImageData:@"http://h.hiphotos.baidu.com/image/pic/item/dc54564e9258d109a4d1165ad558ccbf6c814d23.jpg"];
        dispatch_async(main_queue, ^{
            self.imageViewTwo.image = image;
        });
    });


    // 列印結果分析:1、 12 執行玩完 執行3  再執行45  2、 12順序不定 45順序不定
    /**
     2016-08-25 10:47:00.560 YJGCDDemo[4436:367515] 下載圖片2 --- <NSThread: 0x7ff720f13ac0>{number = 47, name = (null)}
     2016-08-25 10:47:00.560 YJGCDDemo[4436:367518] 下載圖片1 --- <NSThread: 0x7ff720e1c5d0>{number = 48, name = (null)}
     2016-08-25 10:47:00.560 YJGCDDemo[4436:367102] dispatch_barrier_async 下載圖片3  --- <NSThread: 0x7ff720d9b7d0>{number = 43, name = (null)}
     2016-08-25 10:47:00.560 YJGCDDemo[4436:367293] 下載圖片4 --- <NSThread: 0x7ff720c9e250>{number = 45, name = (null)}
     2016-08-25 10:47:00.560 YJGCDDemo[4436:367516] 下載圖片5 --- <NSThread: 0x7ff720dba290>{number = 46, name = (null)}

     */



}

七、程式碼示例 在Demo中的CommonMethodsViewCotroller類
下載兩張照片完後,合併照片。(兩種方式)
這裡寫圖片描述

/**
 *  合併圖片(方式一)
 */
- (void)mergeImage{

//    // 獲取全域性併發佇列
//    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//
//    // 獲取主佇列
//    dispatch_queue_t mainQueue = dispatch_get_main_queue();


    dispatch_async(global_queue, ^{
        // 下載圖片1
        UIImage *image1 = [self requestImageData:@"http://h.hiphotos.baidu.com/image/pic/item/dc54564e9258d109a4d1165ad558ccbf6c814d23.jpg"];
        NSLog(@"圖片1下載完成----%@", [NSThread currentThread]);

        UIImage *image2 = [self requestImageData:@"http://5.26923.com/download/pic/000/335/06efd7b7d40328f1470d4fd99a214243.jpg"];

        NSLog(@"圖片2下載完成----%@", [NSThread currentThread]);

        // 回到主執行緒顯示圖片
        dispatch_async(main_queue, ^{
            NSLog(@"顯示圖片---%@", [NSThread currentThread]);
            self.imageViewOne.image = image1;
            self.imageViewTwo.image = image2;

            // 合併兩張圖片
            UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 200), NO, 0.0);
            [image1 drawAsPatternInRect:CGRectMake(0, 0, 100, 200)];
            [image2 drawAsPatternInRect:CGRectMake(100, 0, 100, 200)];
            self.imageViewThree.image = UIGraphicsGetImageFromCurrentImageContext();
            // 關閉上下文
            UIGraphicsEndImageContext();

            NSLog(@"圖片合併完成----%@", [NSThread currentThread]);

        });
    });

    // 列印結果 需要等圖片1下載完 在下載圖片2 再回到主執行緒
    /**
     2016-08-24 16:58:40.123 YJGCDDemo[3480:125975] 圖片1下載完成----<NSThread: 0x7ff65acd85f0>{number = 3, name = (null)}
     2016-08-24 16:58:40.319 YJGCDDemo[3480:125975] 圖片2下載完成----<NSThread: 0x7ff65acd85f0>{number = 3, name = (null)}
     2016-08-24 16:58:40.319 YJGCDDemo[3480:125910] 顯示圖片---<NSThread: 0x7ff65ad00dd0>{number = 1, name = main}
     2016-08-24 16:58:40.335 YJGCDDemo[3480:125910] 圖片合併完成----<NSThread: 0x7ff65ad00dd0>{number = 1, name = main}
     */
    // 效率不高 需要等圖片1,圖片2都下載完了後才合併
    // 優化 使用佇列組可以讓圖片1 圖片2的下載任務同事進行,且當兩個下載任務都完成的時候回到主執行緒進行顯示。


}
/**
 *  使用佇列組解決(方式二)
 */
- (void)groupMergeImage{
    //步驟
    //  1、建立一個佇列組
    //  2、開啟一個任務下載圖片1
    //  3、開啟一個任務下載圖片2
    //  同時執行下載圖片1  和 下載圖片2操作
    //  4、等group中的所有任務都執行完畢,再回到主執行緒執行其他操作

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

    // 2、開啟一個任務下載圖片1
    __block UIImage *image1 = nil;
    dispatch_group_async(group, global_queue, ^{
        image1 = [self requestImageData:@"http://h.hiphotos.baidu.com/image/pic/item/dc54564e9258d109a4d1165ad558ccbf6c814d23.jpg"];
        NSLog(@"圖片1下載完成--- %@", [NSThread currentThread]);
    });

    // 3、開啟一個任務下載圖片2
    __block UIImage *image2 = nil;
    dispatch_group_async(group, global_queue, ^{
        image2 = [self requestImageData:@"http://5.26923.com/download/pic/000/335/06efd7b7d40328f1470d4fd99a214243.jpg"];
        NSLog(@"圖片2下載完成--- %@", [NSThread currentThread]);
    });

    // 同時執行下載圖片1\下載圖片2操作

    // 4、等group重的所有任務都執行完畢,再回到主執行緒執行其他操作
    dispatch_group_notify(group, main_queue, ^{
        NSLog(@"顯示圖 --- %@", [NSThread currentThread]);
        self.imageViewOne.image = image1;
        self.imageViewTwo.image = image2;

        // 合併兩張圖片
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 200), NO, 0.0);
        [image1 drawAsPatternInRect:CGRectMake(0, 0, 100, 200)];
        [image2 drawAsPatternInRect:CGRectMake(100, 0, 100, 200)];
        self.imageViewThree.image = UIGraphicsGetImageFromCurrentImageContext();
        // 關閉上下文
        UIGraphicsEndImageContext();

        NSLog(@"圖片合併完成 --- %@", [NSThread currentThread]);
    });


    // 同時開啟兩個執行緒 分別下載圖片 會比上面的效率高一點
    /**
     2016-08-24 16:58:03.785 YJGCDDemo[3467:125346] 圖片1下載完成--- <NSThread: 0x7f8d13cc61c0>{number = 3, name = (null)}
     2016-08-24 16:58:03.978 YJGCDDemo[3467:125349] 圖片2下載完成--- <NSThread: 0x7f8d13ece620>{number = 4, name = (null)}
     2016-08-24 16:58:03.978 YJGCDDemo[3467:125303] 顯示圖 --- <NSThread: 0x7f8d13e052b0>{number = 1, name = main}
     2016-08-24 16:58:03.995 YJGCDDemo[3467:125303] 圖片合併完成 --- <NSThread: 0x7f8d13e052b0>{number = 1, name = main}

     */

}

八、執行緒間通訊

// 從子執行緒回到主執行緒
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     // 執行耗時的非同步操作
     dispatch_async(dispatch_get_main_queue(), ^{
          //回到主執行緒,執行UI重新整理操作
     });
 });

九、Operation和GCD的對比
優點: 不需要關心執行緒管理,資料同步的問題;
區別:
1、效能:GCD更接近底層,而NSOperation則更高階抽象,所以GCD在追求效能的底層操作來說,是速度最快的。
2、從非同步操作之間的事務性,順序行,依賴關係。GCD需要自己寫更多的程式碼來實現,而NSOperationQueue已經內建了這些支援
3、如果非同步操作的國學更多的被互動和UI呈現出來,NSOperationQueue會是一個更好的選擇。底層程式碼中,任務之間不太互相依賴,而需要更高的併發能力,GCD則更有優勢

十、總結
學了兩天,對gcd有一些的瞭解,能在專案中使用多執行緒,不過這邊也要避免很多死鎖的問題,後期有時間再整理出來。四天左右把iOS多執行緒的幾種方法都整理了下,寫了demo。也算對自己的一個小小總結。
總結兩點:
1、執行緒執行方式
dispatch_async 非同步執行
dispatch_sync 同步執行
dispatch_delay 延遲執行
2、處理任務物件
dispatch_get_main_queue 主執行緒佇列(UI執行緒佇列)
dispatch_get_global_queue 併發執行緒佇列
序列佇列