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

多線程-GCD.png

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

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

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

1.NSThread(OC)

2.GCD(C語言)

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

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

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

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

在GCD中扶平,有兩個(gè)重要的概念:任務(wù)和隊(duì)列。我們在使用GCD的時(shí)候蔬蕊,都是在與這兩個(gè)概念打交道结澄,如果這兩個(gè)概念不清楚,我們使用的時(shí)候就會(huì)混淆。下面呢麻献,我們就一起學(xué)習(xí)這兩個(gè)重要的概念吧们妥。

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

什么是任務(wù)勉吻?
我們上學(xué)的時(shí)候监婶,老師說,給你布置一項(xiàng)任務(wù)吧齿桃,今天負(fù)責(zé)收取全班同學(xué)的作業(yè)惑惶。收取作業(yè)這個(gè)操作就是任務(wù)。所以短纵,任務(wù)是什么带污?就是要執(zhí)行的操作。很好理解吧香到?
什么是同步(sync)鱼冀?
不開啟新的線程,把任務(wù)添加到當(dāng)前線程中悠就,在任務(wù)完成之前一直等待雷绢。
什么是異步(async)?
可以開啟新的線程理卑,任務(wù)可以添加到新的線程中翘紊,不等待隊(duì)列完成,可以繼續(xù)執(zhí)行藐唠。
舉個(gè)例子??:
假如你要做午飯這個(gè)任務(wù)帆疟。
同步執(zhí)行就是你只有一個(gè)鍋,一個(gè)爐子宇立,只能先把米飯做熟踪宠,然后再去炒菜。
異步執(zhí)行就是你有兩個(gè)鍋妈嘹,兩個(gè)爐子柳琢,一個(gè)鍋里做米飯,一個(gè)鍋里炒菜润脸。

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

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

隊(duì)列(Dispatch Queue)是什么倒堕?
隊(duì)列也很好理解的。我們都見過排隊(duì)爆价,排隊(duì)買飯垦巴、排隊(duì)取票媳搪、排隊(duì)上車等等。排隊(duì)的時(shí)候骤宣,總是隊(duì)伍前面的先進(jìn)入秦爆,后面的后進(jìn)入,這就是先進(jìn)先出(FIFO)憔披。
隊(duì)列的示意圖如下:


隊(duì)列示意圖.png

GCD中的隊(duì)列等限,就是對多個(gè)線程進(jìn)行管理的。
在隊(duì)列中活逆,還有兩個(gè)概念,串行隊(duì)列和并發(fā)隊(duì)列拗胜。
什么是串行隊(duì)列蔗候?
我們都吃過糖葫蘆,一串一串的埂软,我們也吃過烤串锈遥,也是一串一串的,總是先吃上面的在吃下面的勘畔,一個(gè)一個(gè)的吃所灸。串行隊(duì)列也是類似的,一個(gè)任務(wù)完成了炫七,再進(jìn)行下一個(gè)任務(wù)爬立。


串行隊(duì)列.png

什么是并發(fā)隊(duì)列?
可以開啟多個(gè)線程万哪,讓任務(wù)并發(fā)執(zhí)行侠驯。但是,并發(fā)隊(duì)列只有在異步任務(wù)的時(shí)候才會(huì)有效奕巍。


并發(fā)+異步.png

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

4. GCD的使用

GCD的使用步驟:
1》創(chuàng)建隊(duì)列(串行的止、并發(fā))檩坚;
2》定制任務(wù)(同步、異步)(想要做的事)诅福;
3》將任務(wù)添加到隊(duì)列中等待執(zhí)行匾委。
GCD會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出來,放到對應(yīng)的線程中執(zhí)行氓润。任務(wù)取出原則還是先進(jìn)先出剩檀。

創(chuàng)建隊(duì)列

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

//隊(duì)列
-(void)test{

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

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

1.同步任務(wù)+串行隊(duì)列
//1.同步任務(wù)+串行隊(duì)列
- (IBAction)gcdTest1:(id)sender {
    //當(dāng)前線程在主線程
   [self test1_1];
    /**
    總結(jié):同步任務(wù)是不開啟新的線程,把任務(wù)添加到當(dāng)前線程中担租,當(dāng)前線程是主線程砸民,所以是添加到主線程中.串行隊(duì)列是任務(wù)一個(gè)接一個(gè)的執(zhí)行。這里有三個(gè)任務(wù)奋救,也是先執(zhí)行任務(wù)一岭参,然后執(zhí)行任務(wù)二,最后是任務(wù)三尝艘。
     打印結(jié)果:
     2020-09-29 15:03:58.965539+0800 多線程[3069:121374] 開始測試?yán)玻?     2020-09-29 15:03:58.965869+0800 多線程[3069:121374] 1.同步任務(wù)+串行隊(duì)列
     2020-09-29 15:03:58.966169+0800 多線程[3069:121374] 當(dāng)前線程:<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] 當(dāng)前線程:<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] 當(dāng)前線程:<NSThread: 0x600000478980>{number = 1, name = main}
     2020-09-29 15:04:00.968428+0800 多線程[3069:121374] 測試結(jié)束啦!
     */
    
    //我們自己創(chuàng)建個(gè)線程試試看
    [NSThread detachNewThreadWithBlock:^{
        [self test1_1];
    }];
    //結(jié)果與上次一樣
    /**
     2020-09-29 15:30:53.381501+0800 多線程[3253:131992] 開始測試?yán)玻?     2020-09-29 15:30:53.381831+0800 多線程[3253:131992] 1.同步任務(wù)+串行隊(duì)列
     2020-09-29 15:30:53.382813+0800 多線程[3253:131992] 當(dāng)前線程:<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] 當(dāng)前線程:<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] 當(dāng)前線程:<NSThread: 0x600003f53680>{number = 7, name = (null)}
     2020-09-29 15:30:55.387616+0800 多線程[3253:131992] 測試結(jié)束啦秒际!
     */
}

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

示意圖:


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

打印結(jié)果:


image.png

總結(jié):同步任務(wù),把任務(wù)添加到當(dāng)前線程尖啡,當(dāng)前在主線程锐峭,所以三個(gè)任務(wù)都添加到主線程中執(zhí)行。同步任務(wù)不具備開啟新線程的能力可婶,所以沒有新的線程沿癞。

示意圖如下:


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

打印結(jié)果:


image.png

總結(jié):
異步任務(wù)可以開啟新的線程椎扬,也不會(huì)阻塞當(dāng)前線程。當(dāng)前是主線程具温,因?yàn)殚_啟新的線程需要時(shí)間蚕涤,所以先打印的主線程的兩句話。開啟線程以后铣猩,因?yàn)槭谴嘘?duì)列揖铜,任務(wù)在隊(duì)列中一個(gè)接一個(gè)的執(zhí)行。
示意圖如下:


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

打印結(jié)果:


image.png

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

示意圖如下:


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

結(jié)果:


死鎖.png

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


image.png

網(wǎng)上很多人會(huì)把上面代碼分為三個(gè)任務(wù)溜哮,但是我認(rèn)為,分為四個(gè)任務(wù)更能夠理解死鎖是如何產(chǎn)生的色解,因?yàn)榧词谷蝿?wù)四不存在茂嗓,依然會(huì)發(fā)生死鎖。

個(gè)人分析:
當(dāng)前在主線程中科阎,任務(wù)一順利執(zhí)行述吸,然后執(zhí)行dispatch_sync這個(gè)函數(shù),也就是任務(wù)二萧恕,函數(shù)體要執(zhí)行的是任務(wù)三刚梭。任務(wù)二要等任務(wù)三執(zhí)行完成才能返回肠阱。因?yàn)橹骶€程是串行的票唆,所以任務(wù)三是在任務(wù)二后面執(zhí)行的。這就造成了任務(wù)二等待任務(wù)三執(zhí)行完返回屹徘,任務(wù)三要等待任務(wù)二返回才能執(zhí)行走趋。它倆就這樣僵持住了,互相等待了噪伊,造成了死鎖簿煌。
示意圖如下:


image.png
6.異步任務(wù)+主隊(duì)列
//6.異步任務(wù)+主隊(duì)列
- (IBAction)gcdTest6:(id)sender {
    NSLog(@"測試開始,異步任務(wù)+主隊(duì)列");
    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é):
我們知道姨伟,主隊(duì)列是串行隊(duì)列,異步任務(wù)可以開啟新的線程豆励,但是不一定會(huì)開啟新的線程夺荒。這里就沒有開啟新的線程,而是把任務(wù)添加到主線程中良蒸。異步任務(wù)不會(huì)阻塞主線程技扼,所以先打印了主線程中的兩句話,然后順序執(zhí)行三個(gè)任務(wù)嫩痰。
示意圖如下:


image.png
7.同步任務(wù)+全局隊(duì)列(也是并發(fā)隊(duì)列)
//7.同步任務(wù)+全局隊(duì)列(也是并發(fā)隊(duì)列)
- (IBAction)gcdTest7:(id)sender {
    NSLog(@"開始剿吻,同步任務(wù)+全局隊(duì)列(也是并發(fā)隊(duì)列)");
    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ù)添加到當(dāng)前線程丽旅,也就是主線程中椰棘。全局隊(duì)列是個(gè)并發(fā)隊(duì)列,并發(fā)隊(duì)列只有在異步任務(wù)的時(shí)候才能起作用魔招,因?yàn)楫?dāng)前只有一個(gè)線程晰搀,并發(fā)是無效果的。因?yàn)橥饺蝿?wù)會(huì)阻塞當(dāng)前線程办斑,直到任務(wù)完成外恕。所以會(huì)先打印最上面的一行在主線程中的代碼,接著阻塞乡翅,執(zhí)行同步任務(wù)鳞疲,也就是任務(wù)一、任務(wù)二和任務(wù)三蠕蚜,直到所有任務(wù)執(zhí)行完了尚洽,在繼續(xù)執(zhí)行主線程的最后一行打印。

示意圖如下:


image.png
8.異步任務(wù)+全局隊(duì)列(也是并發(fā)隊(duì)列)
//8.異步任務(wù)+全局隊(duì)列(也是并發(fā)隊(duì)列)
- (IBAction)gcdTest8:(id)sender {
    NSLog(@"開始靶累,異步任務(wù)+全局隊(duì)列");
    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ù)是可以開啟新線程的腺毫。這里的三個(gè)異步任務(wù)開啟了三個(gè)線程。全局隊(duì)列也是并發(fā)隊(duì)列挣柬,把任務(wù)分發(fā)給三個(gè)子線程中執(zhí)行潮酒。主線程不會(huì)阻塞,所以直接打印了主線程的內(nèi)容邪蛔。開啟子線程需要時(shí)間急黎。由于任務(wù)二睡了2秒,所以先打印的任務(wù)一和任務(wù)三侧到,最后是任務(wù)二勃教。
示意圖如下:


image.png

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


image.png

簡單一點(diǎn):


image.png

觀察之后發(fā)現(xiàn),
同步任務(wù)都不開啟新的線程匠抗,都會(huì)阻塞當(dāng)前線程故源,大部分都是串行執(zhí)行任務(wù)。
異步任務(wù)都不會(huì)阻塞當(dāng)前線程汞贸,大部分會(huì)開啟新的線程绳军。

5. 案例:圖片下載

因?yàn)橄螺d圖片是耗時(shí)操作,一般我們都會(huì)放在子線程中進(jìn)行下載著蛙,等圖片下載完成以后删铃,再把圖片放在主線程中顯示。
下面我們就用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(@"###############我是個(gè)柵欄################");
});

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

//柵欄函數(shù)
- (IBAction)zhalan:(id)sender {
    //柵欄函數(shù),可以控制任務(wù)的執(zhí)行順序
    //1.創(chuàng)建并發(fā)隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊(duì)列", 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ā)隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊(duì)列", 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(@"###############我是個(gè)柵欄################");
    });
    
    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ù)之后育叁,柵欄前面的兩個(gè)任務(wù)先執(zhí)行完,然后執(zhí)行柵欄函數(shù)芍殖,最后在執(zhí)行后面的任務(wù)豪嗽。就像一座柵欄一樣,把異步執(zhí)行的無順序的任務(wù)隔離開了豌骏,變成了部分有序的代碼龟梦。

7. 延遲執(zhí)行

有的時(shí)候,我們需要延遲幾秒后在執(zhí)行某些操作窃躲,這個(gè)時(shí)候计贰,我們就可以使用GCD的dispatch_after來實(shí)現(xiàn)啦。
dispatch_after不會(huì)阻塞當(dāng)前線程框舔,可以在主線程蹦玫,也可以在子線程中執(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(@"當(dāng)前線程:%@",[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(@"當(dāng)前線程:%@",[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í)行的。但是呢完丽,這兩個(gè)方法都是在主線程中執(zhí)行的恋技。

8. 一次性代碼

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

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

因?yàn)榇硕未a常用于單例中,我們創(chuàng)建個(gè)單例試試聘鳞。

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(@"此代碼只會(huì)執(zhí)行一次薄辅。");
    });
    
    //創(chuàng)建個(gè)單例試試
    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)建的四個(gè)對象的地址都是一樣的,這就是一個(gè)合理的單例啦站楚。

9. 快速迭代

我們需要使用循環(huán)的時(shí)候脱惰,一般用for循環(huán),for_in循環(huán)窿春,while循環(huán)拉一,do...while循環(huán),swift中還有更多其他的循環(huán)旧乞。在GCD中舅踪,也給我們提供了一種快速的循環(huán)方法,dispatch_apply良蛮。

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

為什么它是快速迭代呢抽碌?
因?yàn)樗鼤?huì)開啟多個(gè)線程并發(fā)執(zhí)行循環(huán)體內(nèi)的操作。
如果在串行隊(duì)列中决瞳,dispatch_apply就和for循環(huán)一樣順序執(zhí)行货徙,就沒有意義了。
在并發(fā)隊(duì)列中皮胡,dispatch_apply會(huì)開啟多個(gè)線程并發(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. 隊(duì)列組

隊(duì)列組可以在組中的并發(fā)線程都執(zhí)行完成后屡贺,進(jìn)行某些操作蠢棱。

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

看個(gè)簡單例子:

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

看看打印結(jié)果:


image.png

我們在項(xiàng)目中泻仙,也是會(huì)經(jīng)常遇到這樣的問題的。比如量没,我們要顯示一個(gè)頁面玉转,但是這個(gè)頁面有三個(gè)接口,我們需要等待三個(gè)接口的數(shù)據(jù)都返回來之后再進(jìn)行顯示殴蹄。這個(gè)時(shí)候究抓,就可以把三個(gè)網(wǎng)絡(luò)請求放在隊(duì)列組中,等待全部完成后袭灯,在進(jìn)行UI顯示刺下。

這里,我們舉一個(gè)例子吧稽荧。
我們要進(jìn)行圖片合成的操作橘茉,需要下載兩張圖片,等兩張圖片都下載完成了,再把圖片合成一張捺癞。

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

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;
//隊(duì)列組案例:合成圖片
- (IBAction)groupDemo:(id)sender {
    //1.創(chuàng)建隊(duì)列組
    dispatch_group_t group = dispatch_group_create();
    //2.創(chuàng)建并發(fā)隊(duì)列
    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(@"當(dāng)前線程:%@",[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)學(xué)習(xí)完了惕鼓,如有遺漏錯(cuò)失,還請留言指教唐础,謝謝箱歧!
下一節(jié)中,我們將探究NSOperation實(shí)現(xiàn)多線程一膨。
祝大家生活愉快呀邢!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市豹绪,隨后出現(xiàn)的幾起案子价淌,更是在濱河造成了極大的恐慌,老刑警劉巖瞒津,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝉衣,死亡現(xiàn)場離奇詭異,居然都是意外死亡巷蚪,警方通過查閱死者的電腦和手機(jī)病毡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屁柏,“玉大人啦膜,你說我怎么就攤上這事√视鳎” “怎么了僧家?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長似嗤。 經(jīng)常有香客問我啸臀,道長届宠,這世上最難降的妖魔是什么烁落? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮豌注,結(jié)果婚禮上伤塌,老公的妹妹穿的比我還像新娘。我一直安慰自己轧铁,他們只是感情好每聪,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般药薯。 火紅的嫁衣襯著肌膚如雪绑洛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天童本,我揣著相機(jī)與錄音真屯,去河邊找鬼。 笑死穷娱,一個(gè)胖子當(dāng)著我的面吹牛绑蔫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泵额,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼配深,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嫁盲?” 一聲冷哼從身側(cè)響起篓叶,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎羞秤,沒想到半個(gè)月后澜共,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锥腻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年嗦董,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瘦黑。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡京革,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出幸斥,到底是詐尸還是另有隱情匹摇,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布甲葬,位于F島的核電站廊勃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏经窖。R本人自食惡果不足惜坡垫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望画侣。 院中可真熱鬧冰悠,春花似錦、人聲如沸配乱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至桑寨,卻和暖如春伏尼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尉尾。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工烦粒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人代赁。 一個(gè)月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓扰她,卻偏偏與公主長得像,于是被迫代替她去往敵國和親芭碍。 傳聞我的和親對象是個(gè)殘疾皇子徒役,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344