iOS詳解多線程(實現(xiàn)篇——GCD)

多線程-GCD.png

上一節(jié)中,我們學習了蘋果官方提供的面向?qū)ο蟮膶崿F(xiàn)多線程的方法——NSThread。這一節(jié)中,我們學習C語言的實現(xiàn)多線程的方法,GCD,這也是我們項目中經(jīng)常使用的一種方法。
NSThread鏈接:詳解多線程(實現(xiàn)篇——NSThread)
多線程概念篇鏈接:詳解多線程(概念篇——進程、線程以及多線程原理)

源碼鏈接:https://github.com/weiman152/Multithreading.git

多線程的實現(xiàn)方法

1.NSThread(OC)

2.GCD(C語言)

3.NSOperation(OC)
4.C語言的pthread(C語言)
5.其他實現(xiàn)多線程方法

本章主要內(nèi)容提示:

  1. GCD概念
  2. 重要概念:任務(wù)(同步、異步)
  3. 重要概念:隊列(串行、并發(fā))
  4. GCD的使用
  5. 案例:圖片下載
  6. 柵欄函數(shù)
  7. 延遲執(zhí)行
  8. 一次性代碼
    8_1. 一次性代碼案例:單例
  9. 快速迭代
  10. 隊列組
    10_1. 隊列組案例(下載圖片之后合成圖片)
1.GCD概念

GCD的全程為Grand Central Dispatch,簡單翻譯為大中央調(diào)度。也是蘋果官方開發(fā)的解決多線程的一種方式。
GCD是C語言的,充分利用CPU的多核的并行運算的一種解決多線程的方法。

在GCD中,有兩個重要的概念:任務(wù)和隊列。我們在使用GCD的時候,都是在與這兩個概念打交道,如果這兩個概念不清楚,我們使用的時候就會混淆。下面呢,我們就一起學習這兩個重要的概念吧。

2. 重要概念:任務(wù)(同步、異步)

什么是任務(wù)?
我們上學的時候,老師說,給你布置一項任務(wù)吧,今天負責收取全班同學的作業(yè)。收取作業(yè)這個操作就是任務(wù)。所以,任務(wù)是什么?就是要執(zhí)行的操作。很好理解吧?
什么是同步(sync)?
不開啟新的線程,把任務(wù)添加到當前線程中,在任務(wù)完成之前一直等待。
什么是異步(async)?
可以開啟新的線程,任務(wù)可以添加到新的線程中,不等待隊列完成,可以繼續(xù)執(zhí)行。
舉個例子??:
假如你要做午飯這個任務(wù)。
同步執(zhí)行就是你只有一個鍋,一個爐子,只能先把米飯做熟,然后再去炒菜。
異步執(zhí)行就是你有兩個鍋,兩個爐子,一個鍋里做米飯,一個鍋里炒菜。

注意:雖然異步執(zhí)行具備開啟新線程的能力,但是不一定就要開啟新線程。

3. 重要概念:隊列(串行、并發(fā))

隊列(Dispatch Queue)是什么?
隊列也很好理解的。我們都見過排隊,排隊買飯、排隊取票、排隊上車等等。排隊的時候,總是隊伍前面的先進入,后面的后進入,這就是先進先出(FIFO)。
隊列的示意圖如下:


隊列示意圖.png

GCD中的隊列,就是對多個線程進行管理的。
在隊列中,還有兩個概念,串行隊列和并發(fā)隊列。
什么是串行隊列?
我們都吃過糖葫蘆,一串一串的,我們也吃過烤串,也是一串一串的,總是先吃上面的在吃下面的,一個一個的吃。串行隊列也是類似的,一個任務(wù)完成了,再進行下一個任務(wù)。


串行隊列.png

什么是并發(fā)隊列?
可以開啟多個線程,讓任務(wù)并發(fā)執(zhí)行。但是,并發(fā)隊列只有在異步任務(wù)的時候才會有效。


并發(fā)+異步.png

弄明白了這些概念,我們就開始使用GCD創(chuàng)建多線程吧。

4. GCD的使用

GCD的使用步驟:
1》創(chuàng)建隊列(串行、并發(fā));
2》定制任務(wù)(同步、異步)(想要做的事);
3》將任務(wù)添加到隊列中等待執(zhí)行。
GCD會自動將隊列中的任務(wù)取出來,放到對應(yīng)的線程中執(zhí)行。任務(wù)取出原則還是先進先出。

創(chuàng)建隊列

//1. 創(chuàng)建隊列
/**
// 第一個參數(shù)const char *label : C語言字符串,用來標識
// 第二個參數(shù)dispatch_queue_attr_t attr : 隊列的類型
// 并發(fā)隊列:DISPATCH_QUEUE_CONCURRENT
// 串行隊列:DISPATCH_QUEUE_SERIAL 或者 NULL
dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
*/

//隊列
-(void)test{

    //1.1 并發(fā)隊列
    //可以開啟多個線程,任務(wù)并發(fā)執(zhí)行
    dispatch_queue_t BFqueue = dispatch_queue_create("BFqueue", DISPATCH_QUEUE_CONCURRENT);
    //1.2 串行隊列
    //任務(wù)一個接一個的執(zhí)行,在一個線程中
    dispatch_queue_t CXqueue = dispatch_queue_create("CXqueue", DISPATCH_QUEUE_SERIAL);
    //1.3 系統(tǒng)默認提供全局并發(fā)隊列,供使用
    /**
    系統(tǒng)默認全局隊列 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     第一個參數(shù):隊列優(yōu)先級
     #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
     #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認
     #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
     #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺
     第二個參數(shù): 預(yù)留參數(shù)  0
     */
    dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //1.4 獲得主隊列
    dispatch_queue_t mainQ = dispatch_get_main_queue();
    
}
創(chuàng)建任務(wù)
//任務(wù)
-(void)test2{
   //1. 同步任務(wù):立刻開始執(zhí)行
    /**
     第一個參數(shù):隊列
     第二個參數(shù):要執(zhí)行的操作,是個Block
     */
    dispatch_sync(dispatch_get_main_queue(), ^{
        //同步執(zhí)行的任務(wù)
    });
    
    //2. 異步任務(wù):等主線程執(zhí)行完以后,開啟子線程執(zhí)行任務(wù)
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //異步執(zhí)行的任務(wù)
    });
}
任務(wù)和隊列的組合

下面我們把任何和隊列進行排列組合,看看每一種的結(jié)果。
1.同步任務(wù)+串行隊列
2.同步任務(wù)+并發(fā)隊列
3.異步任務(wù)+串行隊列
4.異步任務(wù)+并發(fā)隊列
5.同步任務(wù)+主隊列
6.異步任務(wù)+主隊列
7.同步任務(wù)+全局隊列
8.異步任務(wù)+全局隊列

1.同步任務(wù)+串行隊列
//1.同步任務(wù)+串行隊列
- (IBAction)gcdTest1:(id)sender {
    //當前線程在主線程
   [self test1_1];
    /**
    總結(jié):同步任務(wù)是不開啟新的線程,把任務(wù)添加到當前線程中,當前線程是主線程,所以是添加到主線程中.串行隊列是任務(wù)一個接一個的執(zhí)行。這里有三個任務(wù),也是先執(zhí)行任務(wù)一,然后執(zhí)行任務(wù)二,最后是任務(wù)三。
     打印結(jié)果:
     2020-09-29 15:03:58.965539+0800 多線程[3069:121374] 開始測試啦!
     2020-09-29 15:03:58.965869+0800 多線程[3069:121374] 1.同步任務(wù)+串行隊列
     2020-09-29 15:03:58.966169+0800 多線程[3069:121374] 當前線程:<NSThread: 0x600000478980>{number = 1, name = main}
     2020-09-29 15:04:00.967371+0800 多線程[3069:121374] 同步任務(wù)二+串行,睡了2秒
     2020-09-29 15:04:00.967774+0800 多線程[3069:121374] 當前線程:<NSThread: 0x600000478980>{number = 1, name = main}
     2020-09-29 15:04:00.967987+0800 多線程[3069:121374] 同步任務(wù)三+串行
     2020-09-29 15:04:00.968206+0800 多線程[3069:121374] 當前線程:<NSThread: 0x600000478980>{number = 1, name = main}
     2020-09-29 15:04:00.968428+0800 多線程[3069:121374] 測試結(jié)束啦!
     */
    
    //我們自己創(chuàng)建個線程試試看
    [NSThread detachNewThreadWithBlock:^{
        [self test1_1];
    }];
    //結(jié)果與上次一樣
    /**
     2020-09-29 15:30:53.381501+0800 多線程[3253:131992] 開始測試啦!
     2020-09-29 15:30:53.381831+0800 多線程[3253:131992] 1.同步任務(wù)+串行隊列
     2020-09-29 15:30:53.382813+0800 多線程[3253:131992] 當前線程:<NSThread: 0x600003f53680>{number = 7, name = (null)}
     2020-09-29 15:30:55.386012+0800 多線程[3253:131992] 同步任務(wù)二+串行,睡了2秒
     2020-09-29 15:30:55.386528+0800 多線程[3253:131992] 當前線程:<NSThread: 0x600003f53680>{number = 7, name = (null)}
     2020-09-29 15:30:55.386772+0800 多線程[3253:131992] 同步任務(wù)三+串行
     2020-09-29 15:30:55.387163+0800 多線程[3253:131992] 當前線程:<NSThread: 0x600003f53680>{number = 7, name = (null)}
     2020-09-29 15:30:55.387616+0800 多線程[3253:131992] 測試結(jié)束啦!
     */
}

-(void)test1_1{
    NSLog(@"開始測試啦!");
    //1.同步任務(wù)+串行隊列
    dispatch_queue_t cxQ1 = dispatch_queue_create("串行隊列一", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(cxQ1, ^{
        NSLog(@"1.同步任務(wù)+串行隊列");
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
    dispatch_sync(cxQ1, ^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"同步任務(wù)二+串行,睡了2秒");
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
    dispatch_sync(cxQ1, ^{
        NSLog(@"同步任務(wù)三+串行");
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
    NSLog(@"測試結(jié)束啦!");
}

示意圖:


同步任務(wù)+串行隊列.png
2.同步任務(wù)+并發(fā)隊列
//2.同步任務(wù)+并發(fā)隊列
- (IBAction)gcdTest2:(id)sender {
    NSLog(@"開始測試啦,2.同步任務(wù)+并發(fā)隊列");
    dispatch_queue_t bfQ = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(bfQ, ^{
        NSLog(@"任務(wù)一");
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
    dispatch_sync(bfQ, ^{
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"任務(wù)二");
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
    dispatch_sync(bfQ, ^{
        NSLog(@"任務(wù)三");
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
    NSLog(@"測試結(jié)束啦");
    /**
     總結(jié):同步任務(wù),把任務(wù)添加到當前線程,當前在主線程,所以三個任務(wù)都添加到主線程中執(zhí)行。
     */
}

打印結(jié)果:


image.png

總結(jié):同步任務(wù),把任務(wù)添加到當前線程,當前在主線程,所以三個任務(wù)都添加到主線程中執(zhí)行。同步任務(wù)不具備開啟新線程的能力,所以沒有新的線程。

示意圖如下:


image.png
3.異步任務(wù)+串行隊列
//3.異步任務(wù)+串行隊列
- (IBAction)gcdTest3:(id)sender {
    NSLog(@"開始測試,3.異步任務(wù)+串行隊列");
    dispatch_queue_t cxq = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    dispatch_async(cxq, ^{
        NSLog(@"任務(wù)一");
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
    dispatch_async(cxq, ^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任務(wù)二");
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
    dispatch_async(cxq, ^{
        NSLog(@"任務(wù)三");
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
    NSLog(@"測試結(jié)束啦!");
}

打印結(jié)果:


image.png

總結(jié):
異步任務(wù)可以開啟新的線程,也不會阻塞當前線程。當前是主線程,因為開啟新的線程需要時間,所以先打印的主線程的兩句話。開啟線程以后,因為是串行隊列,任務(wù)在隊列中一個接一個的執(zhí)行。
示意圖如下:


image.png
4.異步任務(wù)+并發(fā)隊列
//4.異步任務(wù)+并發(fā)隊列
- (IBAction)gcdTest4:(id)sender {
    NSLog(@"測試開始,異步任務(wù)+并發(fā)隊列");
    dispatch_queue_t bfq = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(bfq, ^{
        NSLog(@"任務(wù)一");
        NSLog(@"1.當前線程:%@",[NSThread currentThread]);
    });
    dispatch_async(bfq, ^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任務(wù)二");
        NSLog(@"2.當前線程:%@",[NSThread currentThread]);
    });
    dispatch_async(bfq, ^{
        NSLog(@"任務(wù)三");
        NSLog(@"3.當前線程:%@",[NSThread currentThread]);
    });
    NSLog(@"測試結(jié)束了!");
}

打印結(jié)果:


image.png

總結(jié):
異步任務(wù)具備開啟新的線程的能力,此案例中有三個任務(wù),開啟了三個線程。并發(fā)隊列把任務(wù)分配給子線程中執(zhí)行。

示意圖如下:


image.png
5.同步任務(wù)+主隊列(死鎖??)
//5.同步任務(wù)+主隊列:死鎖
- (IBAction)gcdTest5:(id)sender {
    NSLog(@"測試開始,同步任務(wù)+主隊列");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"任務(wù)一");
        NSLog(@"1.線程:%@",[NSThread currentThread]);
    });
    NSLog(@"測試結(jié)束");
}

結(jié)果:


死鎖.png

為什么嘞?
我們把代碼按照任務(wù)詳細分一下:


image.png

網(wǎng)上很多人會把上面代碼分為三個任務(wù),但是我認為,分為四個任務(wù)更能夠理解死鎖是如何產(chǎn)生的,因為即使任務(wù)四不存在,依然會發(fā)生死鎖。

個人分析:
當前在主線程中,任務(wù)一順利執(zhí)行,然后執(zhí)行dispatch_sync這個函數(shù),也就是任務(wù)二,函數(shù)體要執(zhí)行的是任務(wù)三。任務(wù)二要等任務(wù)三執(zhí)行完成才能返回。因為主線程是串行的,所以任務(wù)三是在任務(wù)二后面執(zhí)行的。這就造成了任務(wù)二等待任務(wù)三執(zhí)行完返回,任務(wù)三要等待任務(wù)二返回才能執(zhí)行。它倆就這樣僵持住了,互相等待了,造成了死鎖。
示意圖如下:


image.png
6.異步任務(wù)+主隊列
//6.異步任務(wù)+主隊列
- (IBAction)gcdTest6:(id)sender {
    NSLog(@"測試開始,異步任務(wù)+主隊列");
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"任務(wù)一");
        NSLog(@"1.線程:%@",[NSThread currentThread]);
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任務(wù)二");
        NSLog(@"2.線程:%@",[NSThread currentThread]);
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"任務(wù)三");
        NSLog(@"3.線程:%@",[NSThread currentThread]);
    });
    NSLog(@"測試結(jié)束。");
}

打印結(jié)果:


image.png

總結(jié):
我們知道,主隊列是串行隊列,異步任務(wù)可以開啟新的線程,但是不一定會開啟新的線程。這里就沒有開啟新的線程,而是把任務(wù)添加到主線程中。異步任務(wù)不會阻塞主線程,所以先打印了主線程中的兩句話,然后順序執(zhí)行三個任務(wù)。
示意圖如下:


image.png
7.同步任務(wù)+全局隊列(也是并發(fā)隊列)
//7.同步任務(wù)+全局隊列(也是并發(fā)隊列)
- (IBAction)gcdTest7:(id)sender {
    NSLog(@"開始,同步任務(wù)+全局隊列(也是并發(fā)隊列)");
    dispatch_queue_t global = dispatch_get_global_queue(0, 0);
    dispatch_sync(global, ^{
        NSLog(@"任務(wù)一");
        NSLog(@"1.線程:%@",[NSThread currentThread]);
    });
    dispatch_sync(global, ^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任務(wù)二");
        NSLog(@"2.線程:%@",[NSThread currentThread]);
    });
    dispatch_sync(global, ^{
        NSLog(@"任務(wù)三");
        NSLog(@"3.線程:%@",[NSThread currentThread]);
    });
    NSLog(@"測試結(jié)束了!");
}

結(jié)果:


image.png

總結(jié):
同步任務(wù)不開啟新的線程,而是把任務(wù)添加到當前線程,也就是主線程中。全局隊列是個并發(fā)隊列,并發(fā)隊列只有在異步任務(wù)的時候才能起作用,因為當前只有一個線程,并發(fā)是無效果的。因為同步任務(wù)會阻塞當前線程,直到任務(wù)完成。所以會先打印最上面的一行在主線程中的代碼,接著阻塞,執(zhí)行同步任務(wù),也就是任務(wù)一、任務(wù)二和任務(wù)三,直到所有任務(wù)執(zhí)行完了,在繼續(xù)執(zhí)行主線程的最后一行打印。

示意圖如下:


image.png
8.異步任務(wù)+全局隊列(也是并發(fā)隊列)
//8.異步任務(wù)+全局隊列(也是并發(fā)隊列)
- (IBAction)gcdTest8:(id)sender {
    NSLog(@"開始,異步任務(wù)+全局隊列");
    dispatch_queue_t global = dispatch_get_global_queue(0, 0);
    dispatch_async(global, ^{
        NSLog(@"任務(wù)一");
        NSLog(@"1.線程:%@",[NSThread currentThread]);
    });
    dispatch_async(global, ^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任務(wù)二");
        NSLog(@"2.線程:%@",[NSThread currentThread]);
    });
    dispatch_async(global, ^{
        NSLog(@"任務(wù)三");
        NSLog(@"3.線程:%@",[NSThread currentThread]);
    });
    
    NSLog(@"測試結(jié)束了");
}

打印結(jié)果:


image.png

總結(jié):
異步任務(wù)是可以開啟新線程的。這里的三個異步任務(wù)開啟了三個線程。全局隊列也是并發(fā)隊列,把任務(wù)分發(fā)給三個子線程中執(zhí)行。主線程不會阻塞,所以直接打印了主線程的內(nèi)容。開啟子線程需要時間。由于任務(wù)二睡了2秒,所以先打印的任務(wù)一和任務(wù)三,最后是任務(wù)二。
示意圖如下:


image.png

GCD的任務(wù)和隊列的總結(jié):


image.png

簡單一點:


image.png

觀察之后發(fā)現(xiàn),
同步任務(wù)都不開啟新的線程,都會阻塞當前線程,大部分都是串行執(zhí)行任務(wù)。
異步任務(wù)都不會阻塞當前線程,大部分會開啟新的線程。

5. 案例:圖片下載

因為下載圖片是耗時操作,一般我們都會放在子線程中進行下載,等圖片下載完成以后,再把圖片放在主線程中顯示。
下面我們就用GCD演示這一過程。

//案例:下載圖片,并顯示
- (IBAction)downLoad:(id)sender {
    NSString * url = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1601469152745&di=b68e17d74c30d400e1df9473ded0eb1c&imgtype=0&src=http%3A%2F%2Fhbimg.huabanimg.com%2F959c8754d77244788f5a8f775ee36dec4a5d362e236d9-eP3CI9_fw658";
    dispatch_queue_t global = dispatch_get_global_queue(0, 0);
    dispatch_async(global, ^{
        NSURL * imgUrl = [NSURL URLWithString:url];
        NSData * data = [NSData dataWithContentsOfURL:imgUrl];
        UIImage *image = [UIImage imageWithData:data];
        NSLog(@"下載圖的線程:%@",[NSThread currentThread]);
        //回到主線程,給圖片賦值
        dispatch_async(dispatch_get_main_queue(), ^{
            self.showImg.image = image;
            NSLog(@"顯示圖的線程:%@",[NSThread currentThread]);
        });
    });
}

結(jié)果:


待下載.png
下載完.png

打印結(jié)果:


image.png
6. 柵欄函數(shù)

dispatch_barrier_async(queue, ^{
NSLog(@"###############我是個柵欄################");
});

柵欄函數(shù)的作用是,控制任務(wù)的執(zhí)行順序.
我們用代碼試試看,我們使用異步任務(wù)+并發(fā)隊列。

//柵欄函數(shù)
- (IBAction)zhalan:(id)sender {
    //柵欄函數(shù),可以控制任務(wù)的執(zhí)行順序
    //1.創(chuàng)建并發(fā)隊列
    dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
    //2.創(chuàng)建任務(wù),異步任務(wù)
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)一");
        [self run:@"1"];
    });
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)二");
        [self run:@"2"];
    });
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)三");
        [self run:@"3"];
    });
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)四");
        [self run:@"4"];
    });
    
}

-(void)run:(NSString *)mark {
    for (int i = 0; i<5; i++) {
        NSLog(@"任務(wù):%@,i = %d, 線程:%@",mark,i,[NSThread currentThread]);
    }
}

打印結(jié)果:


image.png

并發(fā)執(zhí)行的線程,任務(wù)的順序是不可控的。

我們使用柵欄函數(shù)后看看。

//柵欄函數(shù)
- (IBAction)zhalan:(id)sender {
    //柵欄函數(shù),可以控制任務(wù)的執(zhí)行順序
    //1.創(chuàng)建并發(fā)隊列
    dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
    //2.創(chuàng)建任務(wù),異步任務(wù)
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)一");
        [self run:@"1"];
    });
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)二");
        [self run:@"2"];
    });
    //柵欄函數(shù)
    dispatch_barrier_async(queue, ^{
        NSLog(@"###############我是個柵欄################");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)三");
        [self run:@"3"];
    });
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)四");
        [self run:@"4"];
    });
    
}

-(void)run:(NSString *)mark {
    for (int i = 0; i<5; i++) {
        NSLog(@"任務(wù):%@,i = %d, 線程:%@",mark,i,[NSThread currentThread]);
    }
}
image.png

image.png

我們發(fā)現(xiàn),使用柵欄函數(shù)之后,柵欄前面的兩個任務(wù)先執(zhí)行完,然后執(zhí)行柵欄函數(shù),最后在執(zhí)行后面的任務(wù)。就像一座柵欄一樣,把異步執(zhí)行的無順序的任務(wù)隔離開了,變成了部分有序的代碼。

7. 延遲執(zhí)行

有的時候,我們需要延遲幾秒后在執(zhí)行某些操作,這個時候,我們就可以使用GCD的dispatch_after來實現(xiàn)啦。
dispatch_after不會阻塞當前線程,可以在主線程,也可以在子線程中執(zhí)行代碼。

//延遲執(zhí)行
- (IBAction)yanchi:(id)sender {
    //在主線程中延遲2秒執(zhí)行
    NSLog(@"開始啦!");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"哈哈哈哈哈,延遲了嘛");
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
    NSLog(@"結(jié)束啦");
    
    //子線程中延遲3秒執(zhí)行
    NSLog(@"再次開始啦");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
        NSLog(@"哎呀呀,3秒哦");
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
    NSLog(@"又一次結(jié)束啦!");
}

結(jié)果:


image.png

延遲執(zhí)行,我們還有其他的方法,比如:

//延遲執(zhí)行的其他方法
    [self performSelector:@selector(YC) withObject:nil afterDelay:2.0];
    
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(YC) userInfo:nil repeats:NO];

-(void)YC {
    NSLog(@"延遲執(zhí)行喲,線程:%@",[NSThread currentThread]);
}

結(jié)果:


image.png

也是可以延遲執(zhí)行的。但是呢,這兩個方法都是在主線程中執(zhí)行的。

8. 一次性代碼

dispatch_once
我們在創(chuàng)建單例的時候經(jīng)常使用GCD的dispatch_once這個方法,保證代碼只會執(zhí)行一次。

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"此代碼只會執(zhí)行一次。");
    });

因為此段代碼常用于單例中,我們創(chuàng)建個單例試試。

8_1. 一次性代碼案例:單例
image.png

代碼如下:
GCDTest.h

//
//  GCDTest.h
//  Multithreading
//
//  Created by wenhuanhuan on 2020/10/2.
//  Copyright ? 2020 weiman. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface GCDTest : NSObject<NSCopying,NSMutableCopying>

+(instancetype)shareGCDTest;

@end

NS_ASSUME_NONNULL_END

GCDTest.m

//
//  GCDTest.m
//  Multithreading
//
//  Created by wenhuanhuan on 2020/10/2.
//  Copyright ? 2020 weiman. All rights reserved.
//

#import "GCDTest.h"

@implementation GCDTest

+(instancetype)shareGCDTest{
    return [[self alloc] init];
}

static GCDTest * instance;
+(instancetype)allocWithZone:(struct _NSZone *)zone{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (instance==nil) {
            instance = [super allocWithZone:zone];
        }
    });
    return instance;
}

-(id)copyWithZone:(NSZone *)zone{
    return instance;
}

-(id)mutableCopyWithZone:(NSZone *)zone{
    return instance;
}
@end

測試:

//一次性代碼
- (IBAction)onceAction:(id)sender {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"此代碼只會執(zhí)行一次。");
    });
    
    //創(chuàng)建個單例試試
    GCDTest * test1 = [[GCDTest alloc] init];
    GCDTest * test2 = [GCDTest shareGCDTest];
    GCDTest * test3 = [test1 copy];
    GCDTest * test4 = [test1 mutableCopy];
    NSLog(@"test1:%@",test1);
    NSLog(@"test2:%@",test2);
    NSLog(@"test3:%@",test3);
    NSLog(@"test4:%@",test4);
}

看看結(jié)果:


image.png

我們發(fā)現(xiàn),我們創(chuàng)建的四個對象的地址都是一樣的,這就是一個合理的單例啦。

9. 快速迭代

我們需要使用循環(huán)的時候,一般用for循環(huán),for_in循環(huán),while循環(huán),do...while循環(huán),swift中還有更多其他的循環(huán)。在GCD中,也給我們提供了一種快速的循環(huán)方法,dispatch_apply。

/*
第一個參數(shù):迭代的次數(shù)
第二個參數(shù):在哪個隊列中執(zhí)行
第三個參數(shù):block要執(zhí)行的任務(wù)
*/
dispatch_apply(10, queue, ^(size_t index) {
});

為什么它是快速迭代呢?
因為它會開啟多個線程并發(fā)執(zhí)行循環(huán)體內(nèi)的操作。
如果在串行隊列中,dispatch_apply就和for循環(huán)一樣順序執(zhí)行,就沒有意義了。
在并發(fā)隊列中,dispatch_apply會開啟多個線程并發(fā)執(zhí)行,提高效率。

//快速迭代
- (IBAction)kuaisu:(id)sender {
    NSLog(@"開始快速迭代");
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"index=%zd, 線程:%@",index, [NSThread currentThread]);
    });
    
}

結(jié)果:


image.png
10. 隊列組

隊列組可以在組中的并發(fā)線程都執(zhí)行完成后,進行某些操作。

// 創(chuàng)建隊列組
    dispatch_group_t group = dispatch_group_create();
    // 創(chuàng)建并行隊列 
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 執(zhí)行隊列組任務(wù)
    dispatch_group_async(group, queue, ^{   
    });
    //隊列組中的任務(wù)執(zhí)行完畢之后,執(zhí)行該函數(shù)
    dispatch_group_notify(group, queue, ^{
    });

看個簡單例子:

//隊列組
- (IBAction)duilie:(id)sender {
    //創(chuàng)建隊列組
    dispatch_group_t group = dispatch_group_create();
    //創(chuàng)建并發(fā)隊列
    dispatch_queue_t queue = dispatch_queue_create("并發(fā)", DISPATCH_QUEUE_CONCURRENT);
    //執(zhí)行隊列組任務(wù)
    dispatch_group_async(group, queue, ^{
        NSLog(@"這是個隊列組中的任務(wù),編號1");
        NSLog(@"任務(wù)一:線程%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"這是個隊列組中的任務(wù),編號2,睡了1秒");
        NSLog(@"任務(wù)二:線程%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"這是個隊列組中的任務(wù),編號3");
        NSLog(@"任務(wù)三:線程%@",[NSThread currentThread]);
    });
    //隊列組執(zhí)行完之后執(zhí)行的函數(shù)
    dispatch_group_notify(group, queue, ^{
        NSLog(@"隊列組任務(wù)執(zhí)行完成。");
    });
}

看看打印結(jié)果:


image.png

我們在項目中,也是會經(jīng)常遇到這樣的問題的。比如,我們要顯示一個頁面,但是這個頁面有三個接口,我們需要等待三個接口的數(shù)據(jù)都返回來之后再進行顯示。這個時候,就可以把三個網(wǎng)絡(luò)請求放在隊列組中,等待全部完成后,在進行UI顯示。

這里,我們舉一個例子吧。
我們要進行圖片合成的操作,需要下載兩張圖片,等兩張圖片都下載完成了,再把圖片合成一張。

10_1. 隊列組案例(下載圖片之后合成圖片)

UI如下:


image.png

代碼如下:

@property (weak, nonatomic) IBOutlet UIImageView *imageOne;
@property (weak, nonatomic) IBOutlet UIImageView *imageTwo;
@property (weak, nonatomic) IBOutlet UIImageView *finalImage;
@property (nonatomic,strong)UIImage * image1;
@property (nonatomic,strong)UIImage * image2;
//隊列組案例:合成圖片
- (IBAction)groupDemo:(id)sender {
    //1.創(chuàng)建隊列組
    dispatch_group_t group = dispatch_group_create();
    //2.創(chuàng)建并發(fā)隊列
    dispatch_queue_t queue = dispatch_queue_create("并發(fā)", DISPATCH_QUEUE_CONCURRENT);
    //3.下載圖片一
    dispatch_group_async(group, queue, ^{
        NSString * str = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1601638749466&di=92ace2ffa924fe6063e7a221729006b1&imgtype=0&src=http%3A%2F%2Fpic.autov.com.cn%2Fimages%2Fcms%2F20119%2F6%2F1315280805177.jpg";
        self.image1 = [self loadImage:str];
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageOne.image = self.image1;
        });
        
    });
    //4.下載圖片二
    dispatch_group_async(group, queue, ^{
        NSString * str = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1601638873771&di=07129fd95c56096a4282d3b072594491&imgtype=0&src=http%3A%2F%2Fimg.51miz.com%2Fpreview%2Felement%2F00%2F01%2F12%2F49%2FE-1124994-5FFE5AC7.jpg";
        self.image2 = [self loadImage:str];
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageTwo.image = self.image2;
        });
    });
    //5.合成圖片
    dispatch_group_notify(group, queue, ^{
        //圖形上下文開啟
        UIGraphicsBeginImageContext(CGSizeMake(300, 200));
        
        //圖形二
        [self.image2 drawInRect:CGRectMake(0, 0, 300, 200)];
        //圖形一
        [self.image1 drawInRect:CGRectMake(100, 50, 100, 100)];
        //獲取新的圖片
        UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
        //關(guān)閉上下文
        UIGraphicsEndImageContext();
        //回到主線程,顯示圖片
        dispatch_async(dispatch_get_main_queue(), ^{
            self.finalImage.image = image;
            NSLog(@"完成圖片的合成");
        });
    });
}
//下載圖片
-(UIImage *)loadImage:(NSString *)strUrl {
    NSLog(@"當前線程:%@",[NSThread currentThread]);
    NSURL * url = [NSURL URLWithString:strUrl];
    NSData * data = [NSData dataWithContentsOfURL:url];
    UIImage * image = [UIImage imageWithData:data];
    return image;
}

結(jié)果如下圖:


image.png

注意圖片的順序。

到此為止,GCD的基本內(nèi)容我們已經(jīng)學習完了,如有遺漏錯失,還請留言指教,謝謝!
下一節(jié)中,我們將探究NSOperation實現(xiàn)多線程。
祝大家生活愉快!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容