GCD基礎(chǔ)篇

GCD全稱是Grand Central Dispatch,大中央調(diào)度,是系統(tǒng)級(jí)的線程管理。
GCD源碼
首先附上GCD官方文檔
官方如是介紹:
Execute code concurrently on multicore hardware by submitting work to dispatch queues managed by the system.
翻譯:通過向系統(tǒng)管理的調(diào)度隊(duì)列添加任務(wù),在多核硬件上同時(shí)執(zhí)行代碼。
GCD, operating at the system level, can better accommodate the needs of all running applications, matching them to the available system resources in a balanced fashion.
翻譯: GCD運(yùn)行在系統(tǒng)級(jí),可以更好地滿足所有正在運(yùn)行的應(yīng)用程序的需求,以平衡的方式將它們與可用的系統(tǒng)資源進(jìn)行匹配。

一句話介紹:GCD是系統(tǒng)級(jí)的線程管理(執(zhí)行任務(wù)的性能非常高),會(huì)自動(dòng)利用CPU內(nèi)核(多核),GCD會(huì)自動(dòng)管理線程的周期--創(chuàng)建 調(diào)度 銷毀。

很厲害有木有。做了一些我們在應(yīng)用級(jí)別做不到事。GCD是C語言的,封裝了很多實(shí)用的API,下面我們就一起來學(xué)習(xí)下。

一、隊(duì)列與任務(wù)

  • 隊(duì)列:是一種常用的數(shù)據(jù)結(jié)構(gòu),遵從FIFO的原則
  • 串行隊(duì)列:順序執(zhí)行(一個(gè)任務(wù)完成了,才能從隊(duì)列里面取出下一個(gè)任務(wù))
  • 并發(fā)隊(duì)列:同時(shí)執(zhí)行很多個(gè)任務(wù)(可以同時(shí)取出很多個(gè)任務(wù),只要有線程去執(zhí)行)
  • 同步任務(wù):sync 優(yōu)先級(jí)高,在線程中有執(zhí)行順序,不會(huì)開啟新的線程
  • 異步任務(wù):async優(yōu)先級(jí)低,在線程中執(zhí)行沒有順序,看cpu閑不閑。在主隊(duì)列中不會(huì)開啟新的線程,其他隊(duì)列會(huì)開啟新的線程
  • 主隊(duì)列:dispatch_get_main_queue()系統(tǒng)提供的全局可用的串行隊(duì)列,可以添加異步任務(wù),不能添加同步任務(wù)
  • 全局隊(duì)列: 系統(tǒng)提供的全局可用的并發(fā)隊(duì)列,根據(jù)優(yōu)先級(jí)不同分為四種并發(fā)隊(duì)列

Important : 給主隊(duì)列添加同步任務(wù)會(huì)造成死鎖

1、全局并發(fā)隊(duì)列

dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)

identifier
為此隊(duì)列執(zhí)行的任務(wù)指定的服務(wù)質(zhì)量。 服務(wù)質(zhì)量有助于確定隊(duì)列執(zhí)行任務(wù)的優(yōu)先級(jí),服務(wù)質(zhì)量高<=>優(yōu)先級(jí)高
QoS:quality of service
flags: 預(yù)留參數(shù),0即可

Dispatch Queue Priorities GCD QoS classes (defined in sys/qos.h)
DISPATCH_QUEUE_PRIORITY_HIGH QOS_CLASS_USER_INITIATED
DISPATCH_QUEUE_PRIORITY_DEFAULT QOS_CLASS_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW QOS_CLASS_UTILITY
DISPATCH_QUEUE_PRIORITY_BACKGROUND QOS_CLASS_BACKGROUND
GCD QoS classes (defined in sys/qos.h) Corresponding Foundation QoS classes
QOS_CLASS_USER_INTERACTIVE NSQualityOfServiceUserInteractive
QOS_CLASS_USER_INITIATED NSQualityOfServiceUserInitiated
QOS_CLASS_DEFAULT NSQualityOfServiceDefault
QOS_CLASS_UTILITY NSQualityOfServiceUtility
QOS_CLASS_BACKGROUND NSQualityOfServiceBackground
Global queue Corresponding QoS class
Main thread User-interactive
DISPATCH_QUEUE_PRIORITY_HIGH User-initiated
DISPATCH_QUEUE_PRIORITY_DEFAULT Default
DISPATCH_QUEUE_PRIORITY_LOW Utility
DISPATCH_QUEUE_PRIORITY_BACKGROUND Background
  • QOS_CLASS_USER_INTERACTIVE:User-interactive 與用戶交互的工作,例如在主線程上操作,刷新用戶界面,或執(zhí)行動(dòng)畫。如果工作不迅速發(fā)生,用戶界面可能會(huì)出現(xiàn)凍結(jié)。關(guān)注響應(yīng)性和性能。主線程在這個(gè)級(jí)別工作。
  • QOS_CLASS_USER_INITIATED:User-initiated 用于執(zhí)行用戶觸發(fā)的,并且需要立即獲得結(jié)果,以便進(jìn)一步進(jìn)行用戶交互的任務(wù)。 例如,例如打開保存的文檔或者郵件列表選中之后需要加載電子郵件。
  • QOS_CLASS_UTILITY:可能需要一些時(shí)間才能完成的工作,不需要立即的結(jié)果。實(shí)用任務(wù)通常有一個(gè)對用戶可見的進(jìn)度條。專注于在響應(yīng)能力、性能和能源效率之間提供平衡。 例如,定期進(jìn)行內(nèi)容更新或批量文件操作,如媒體導(dǎo)入。
  • QOS_CLASS_BACKGROUND:在后臺(tái)運(yùn)行的工作,對用戶來說是不可見的,關(guān)注能源效率。比如索引、同步和備份。
  • QOS_CLASS_DEFAULT:這個(gè)QoS的優(yōu)先級(jí)介于User-interactive和User-initiated之間。這個(gè)QoS不打算被開發(fā)人員用來對工作進(jìn)行分類。未分配QoS信息的工作被視為默認(rèn)值,GCD全局隊(duì)列在這個(gè)級(jí)別上運(yùn)行。

【小貼士】 NSOperation的默認(rèn)QoS是NSQualityOfServiceBackground

【優(yōu)先級(jí)反轉(zhuǎn)】
特別注意的是,任務(wù)之間關(guān)系復(fù)雜時(shí),優(yōu)先級(jí)盡量簡單點(diǎn),不然會(huì)造成優(yōu)先級(jí)反轉(zhuǎn),危害很大!
由于優(yōu)先級(jí)反轉(zhuǎn),直接就導(dǎo)致任務(wù)錯(cuò)亂,邏輯錯(cuò)亂。此外造成任務(wù)調(diào)度時(shí),時(shí)間的不確定性。破壞了實(shí)時(shí)系統(tǒng)的實(shí)時(shí)性,嚴(yán)重時(shí)可能導(dǎo)致系統(tǒng)崩潰。

那什么是優(yōu)先級(jí)反轉(zhuǎn)呢?

當(dāng)高優(yōu)先級(jí)的工作依賴于較低優(yōu)先級(jí)的工作時(shí),或者它成為低優(yōu)先級(jí)工作的結(jié)果,則會(huì)發(fā)生優(yōu)先級(jí)反轉(zhuǎn)。結(jié)果,可能會(huì)發(fā)生阻塞、旋轉(zhuǎn)和輪詢。
在同步工作的情況下,系統(tǒng)將通過在反轉(zhuǎn)期間提高低優(yōu)先級(jí)工作的QoS來自動(dòng)解決優(yōu)先級(jí)反轉(zhuǎn)。這將發(fā)生在以下情況:
在串行隊(duì)列上調(diào)用dispatch_sync()和dispatch_wait()時(shí)。
當(dāng)調(diào)用pthread_mutex_lock()時(shí),互斥對象被一個(gè)帶有較低QoS的線程所控制。在這種情況下,持有鎖的線程被提高到調(diào)用者的QoS。但是,這個(gè)QoS升級(jí)不會(huì)出現(xiàn)在多個(gè)鎖之間。
在異步工作的情況下,系統(tǒng)將嘗試解決串行隊(duì)列中出現(xiàn)的優(yōu)先級(jí)反轉(zhuǎn)。

優(yōu)先級(jí)解決辦法?

Developers should try to ensure that priority inversions don’t occur in the first place, so the system isn’t forced to attempt a resolution.

2、自定義隊(duì)列

    //創(chuàng)建一個(gè)非主線程的實(shí)驗(yàn)環(huán)境
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"添加任務(wù)前--當(dāng)前線程--%@",[NSThread currentThread]);
        
        //串行隊(duì)列同步執(zhí)行
        //創(chuàng)建一個(gè)串行隊(duì)列 第一個(gè)參數(shù):給隊(duì)列起一個(gè)名字  第二個(gè)參數(shù):隊(duì)列屬性,串行還是并行
        dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
        //添加異步任務(wù)
        for (NSInteger i = 0 ; i < 5; i++) {
            dispatch_async(serialQueue, ^{
                NSLog(@"串行隊(duì)列異步任務(wù)%ld--當(dāng)前線程--%@",i,[NSThread currentThread]);
            });
        }
        
        //添加同步任務(wù)
        for (NSInteger i = 0 ; i < 5; i++) {
            dispatch_sync(serialQueue, ^{
                NSLog(@"串行隊(duì)列同步任務(wù)%ld--當(dāng)前線程--%@",i,[NSThread currentThread]);
            });
        }
    });
控制臺(tái)輸出
2017-10-24 10:11:19.243669+0800 GCD[21493:746313] 添加任務(wù)前--當(dāng)前線程--<NSThread: 0x600000271e00>{number = 3, name = (null)}
2017-10-24 10:11:19.243890+0800 GCD[21493:746312] 串行隊(duì)列異步任務(wù)0--當(dāng)前線程--<NSThread: 0x600000271b00>{number = 4, name = (null)}
2017-10-24 10:11:19.244002+0800 GCD[21493:746312] 串行隊(duì)列異步任務(wù)1--當(dāng)前線程--<NSThread: 0x600000271b00>{number = 4, name = (null)}
2017-10-24 10:11:19.244152+0800 GCD[21493:746312] 串行隊(duì)列異步任務(wù)2--當(dāng)前線程--<NSThread: 0x600000271b00>{number = 4, name = (null)}
2017-10-24 10:11:19.244313+0800 GCD[21493:746312] 串行隊(duì)列異步任務(wù)3--當(dāng)前線程--<NSThread: 0x600000271b00>{number = 4, name = (null)}
2017-10-24 10:11:19.244572+0800 GCD[21493:746312] 串行隊(duì)列異步任務(wù)4--當(dāng)前線程--<NSThread: 0x600000271b00>{number = 4, name = (null)}
2017-10-24 10:11:19.246669+0800 GCD[21493:746313] 串行隊(duì)列同步任務(wù)0--當(dāng)前線程--<NSThread: 0x600000271e00>{number = 3, name = (null)}
2017-10-24 10:11:19.247604+0800 GCD[21493:746313] 串行隊(duì)列同步任務(wù)1--當(dāng)前線程--<NSThread: 0x600000271e00>{number = 3, name = (null)}
2017-10-24 10:11:19.248015+0800 GCD[21493:746313] 串行隊(duì)列同步任務(wù)2--當(dāng)前線程--<NSThread: 0x600000271e00>{number = 3, name = (null)}
2017-10-24 10:11:19.248445+0800 GCD[21493:746313] 串行隊(duì)列同步任務(wù)3--當(dāng)前線程--<NSThread: 0x600000271e00>{number = 3, name = (null)}
2017-10-24 10:11:19.248746+0800 GCD[21493:746313] 串行隊(duì)列同步任務(wù)4--當(dāng)前線程--<NSThread: 0x600000271e00>{number = 3, name = (null)}

串行隊(duì)列異步任務(wù):會(huì)開辟一個(gè)線程,一個(gè)一個(gè)按照順序執(zhí)行任務(wù)

串行隊(duì)列同步任務(wù):不開辟新的線程,在當(dāng)前線程一個(gè)一個(gè)按照順序執(zhí)行任務(wù)

//創(chuàng)建一個(gè)非主線程的實(shí)驗(yàn)環(huán)境
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //創(chuàng)建并行隊(duì)列
        dispatch_queue_t currentQueue = dispatch_queue_create("currentQueue", DISPATCH_QUEUE_CONCURRENT);
        NSLog(@"添加任務(wù)前--當(dāng)前線程--%@",[NSThread currentThread]);
        //添加異步任務(wù)
        for (NSInteger i = 0 ; i < 5; i++) {
            dispatch_async(currentQueue, ^{
                NSLog(@"并行隊(duì)列異步任務(wù)%ld--當(dāng)前線程--%@",i,[NSThread currentThread]);
            });
        }
        //添加同步任務(wù)
        for (NSInteger i = 0 ; i < 5; i++) {
            dispatch_sync(currentQueue, ^{
                NSLog(@"并行隊(duì)列同步任務(wù)%ld--當(dāng)前線程--%@",i,[NSThread currentThread]);
            });
        }
    });
2017-10-24 10:36:53.944242+0800 GCD[21646:774599] 添加任務(wù)前--當(dāng)前線程--<NSThread: 0x60000007b580>{number = 3, name = (null)}
2017-10-24 10:36:53.944488+0800 GCD[21646:774603] 并行隊(duì)列異步任務(wù)1--當(dāng)前線程--<NSThread: 0x60000027bcc0>{number = 5, name = (null)}
2017-10-24 10:36:53.944493+0800 GCD[21646:774599] 并行隊(duì)列同步任務(wù)0--當(dāng)前線程--<NSThread: 0x60000007b580>{number = 3, name = (null)}
2017-10-24 10:36:53.944489+0800 GCD[21646:774597] 并行隊(duì)列異步任務(wù)2--當(dāng)前線程--<NSThread: 0x60400046e740>{number = 4, name = (null)}
2017-10-24 10:36:53.944628+0800 GCD[21646:774603] 并行隊(duì)列異步任務(wù)3--當(dāng)前線程--<NSThread: 0x60000027bcc0>{number = 5, name = (null)}
2017-10-24 10:36:53.944527+0800 GCD[21646:774596] 并行隊(duì)列異步任務(wù)0--當(dāng)前線程--<NSThread: 0x60000027c040>{number = 6, name = (null)}
2017-10-24 10:36:53.944635+0800 GCD[21646:774599] 并行隊(duì)列同步任務(wù)1--當(dāng)前線程--<NSThread: 0x60000007b580>{number = 3, name = (null)}
2017-10-24 10:36:53.944703+0800 GCD[21646:774598] 并行隊(duì)列異步任務(wù)4--當(dāng)前線程--<NSThread: 0x60400046e900>{number = 7, name = (null)}
2017-10-24 10:36:53.945277+0800 GCD[21646:774599] 并行隊(duì)列同步任務(wù)2--當(dāng)前線程--<NSThread: 0x60000007b580>{number = 3, name = (null)}
2017-10-24 10:36:53.945801+0800 GCD[21646:774599] 并行隊(duì)列同步任務(wù)3--當(dāng)前線程--<NSThread: 0x60000007b580>{number = 3, name = (null)}
2017-10-24 10:36:53.946104+0800 GCD[21646:774599] 并行隊(duì)列同步任務(wù)4--當(dāng)前線程--<NSThread: 0x60000007b580>{number = 3, name = (null)}

并行隊(duì)列異步任務(wù):開辟多個(gè)線程,并發(fā)執(zhí)行,執(zhí)行沒有順序

并行隊(duì)列同步任務(wù):不開辟線程,在當(dāng)前線程按照順序一個(gè)個(gè)執(zhí)行

    /*給自定義隊(duì)列指定優(yōu)先級(jí)
     *attr: 隊(duì)列屬性,串或并
     *qos_class:QoS
     *relative_priority:必須是一個(gè)小于0大于QOS_MIN_RELATIVE_PRIORITY(-15)的數(shù)
     */
    dispatch_queue_attr_t att = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("seiialqueue2", att);
    dispatch_async(serialQueue2, ^{
        //添加需要執(zhí)行的任務(wù)
    });

二、實(shí)用API

1、dispatch_once
一個(gè)application生命周期內(nèi)block內(nèi)只執(zhí)行一次。
【應(yīng)用場景】:創(chuàng)建單例

#import "Test8.h"

@implementation Test8

+ (Test8 *)shareInstance{

    static Test8 * test8 = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        test8 = [[Test8 alloc] init];
    });
    return test8;
}

@end

2、dispatch_time
用于創(chuàng)建一個(gè)相對于默認(rèn)時(shí)鐘創(chuàng)建dispatch_time_t或修改現(xiàn)有的dispatch_function_t。
默認(rèn)時(shí)鐘:是基于mach_absolute_time的。
【應(yīng)用場景】配合其他API實(shí)現(xiàn)定時(shí)、延時(shí)操作等。

dispatch_time_t time = dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>)

第一個(gè)參數(shù):when

 DISPATCH_TIME_NOW//當(dāng)前時(shí)間
 DISPATCH_TIME_FOREVER//一個(gè)很遙遠(yuǎn)的時(shí)間 
 通??梢杂脕砜刂贫〞r(shí)器是否開始

第二個(gè)參數(shù):delta
在時(shí)間參數(shù)when基礎(chǔ)上添加的納秒數(shù)。

1秒(s)=1000000000納秒(ns)
1毫秒(ms)=1000000納秒(ns)
1秒(s)=1000000微秒(μs)
1微秒(μs)=1000納秒(ns)
#define NSEC_PER_SEC 1000000000ull  //每秒有多少納秒
#define NSEC_PER_MSEC 1000000ull    //每毫秒有多少納秒
#define USEC_PER_SEC 1000000ull     //每秒有多少微妙
#define NSEC_PER_USEC 1000ull       //每微秒有多少納秒

所以一秒鐘通常可以這么表示

1*NSEC_PER_SEC
1000*NSEC_PER_MSEC
NSEC_PER_USEC*NSEC_PER_MSEC
NSEC_PER_USEC* USEC_PER_SEC

3、dispatch_after 延時(shí)操作
此函數(shù)等待直到指定的時(shí)間,然后異步地將block添加到指定的隊(duì)列。而不是立即執(zhí)行。
【應(yīng)用場景】頁面加載完成之后,彈出廣告

dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)
 //創(chuàng)建一個(gè)時(shí)間
 dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC);
 dispatch_after(time, dispatch_get_main_queue(), ^{
    //等待time秒 ,block會(huì)被異步提交到主隊(duì)列
        NSLog(@"2-1");
 });

傳遞DISPATCH_TIME_NOW作為when參數(shù)被支持,但不如調(diào)用dispatch_async最佳。 傳遞DISPATCH_TIME_FOREVER未定義。

 dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^{
        NSLog(@"2-1");
    });

4、dispatch_barrier_async
barrier提交的block會(huì)等待barrier之前提交的任務(wù)全部完成,barrier任務(wù)執(zhí)行期間只有它自己執(zhí)行,后加入的任務(wù)等barrier任務(wù)執(zhí)行結(jié)束才會(huì)執(zhí)行。
【注意】dispatch_barrier_async只在自己創(chuàng)建的并發(fā)隊(duì)列上起作用,在全局并發(fā)隊(duì)列和串行隊(duì)列上,效果和dispatch_sync一樣。
【應(yīng)用場景】避免資源競爭 例如 :對同一個(gè)文件進(jìn)行讀寫操作,其中寫操作可以通過dispatch_barrier_async提交。

    dispatch_queue_t currentQueue = dispatch_queue_create("com.starming.gcddemo.secondqueue", DISPATCH_QUEUE_CONCURRENT);
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_async(currentQueue, ^{
            NSLog(@"前置任務(wù)%ld",i);
        });
    }
    dispatch_barrier_async(currentQueue, ^{
        NSLog(@"barrier任務(wù)");
    });
    
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_async(currentQueue, ^{
            NSLog(@"后置任務(wù)%ld",i);
        });
    }

控制臺(tái)輸出

2017-10-26 13:34:35.632559+0800 GCD[30827:2384918] 前置任務(wù)1
2017-10-26 13:34:35.632559+0800 GCD[30827:2384912] 前置任務(wù)0
2017-10-26 13:34:35.632560+0800 GCD[30827:2384915] 前置任務(wù)3
2017-10-26 13:34:35.632771+0800 GCD[30827:2384918] 前置任務(wù)4
2017-10-26 13:34:35.632604+0800 GCD[30827:2384913] 前置任務(wù)2
2017-10-26 13:34:35.634816+0800 GCD[30827:2384913] barrier任務(wù)
2017-10-26 13:34:35.635281+0800 GCD[30827:2384918] 后置任務(wù)0
2017-10-26 13:34:35.635285+0800 GCD[30827:2384912] 后置任務(wù)3
2017-10-26 13:34:35.635287+0800 GCD[30827:2384915] 后置任務(wù)2
2017-10-26 13:34:35.635302+0800 GCD[30827:2384913] 后置任務(wù)1
2017-10-26 13:34:35.635637+0800 GCD[30827:2384914] 后置任務(wù)4

5、dispatch_group_async
dispatch groups可以監(jiān)聽多個(gè)異步任務(wù),即使它們可能在不同的隊(duì)列上運(yùn)行。完成之后可以收到通知。
dispatch_group_notify :異步執(zhí)行閉包,不阻塞線程。 group 監(jiān)聽的任務(wù)全部完成之后,會(huì)調(diào)用notify的block
dispatch_group_wait:阻塞式的,會(huì)等待之前監(jiān)聽的任務(wù)全部完成,成功返回0。
不論哪種方式,成功后group清空,可以繼續(xù)添加任務(wù)。
【應(yīng)用場景】我們公司的APP是混合模式的,應(yīng)用啟動(dòng)會(huì)根據(jù)資源列表下載有變化的資源文件,下載完成之后需要給服務(wù)器反饋下載結(jié)果。
兩種方式的差別如下。

    //創(chuàng)建三個(gè)并發(fā)隊(duì)列
    dispatch_queue_t current1 = dispatch_queue_create("current1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t current2 = dispatch_queue_create("current2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t current3 = dispatch_queue_create("current3", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    
    //下載圖片
    dispatch_group_async(group, current1, ^{
        NSLog(@"group任務(wù)下載圖片1---currentThread%@",[NSThread currentThread]);
    });
    //下載圖片
    dispatch_group_async(group, current2, ^{
        NSLog(@"group任務(wù)下載圖片2---currentThread%@",[NSThread currentThread]);
    });
    //下載圖片
    dispatch_group_async(group, current3, ^{
        NSLog(@"group任務(wù)下載圖片3---currentThread%@",[NSThread currentThread]);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"圖片下載完成,主線程展示");
    });
  
//    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//    NSLog(@"圖片下載完成,主線程展示");

    //再監(jiān)聽一個(gè)任務(wù)
    /**dispatch_group_enter
     *可以讓應(yīng)用程序通過除了使用dispatch_group_async功能之外的方式顯式添加和刪除組中的任務(wù),
     *從而正確地管理任務(wù)引用計(jì)數(shù)。 調(diào)用此函數(shù)必須與調(diào)用dispatch_group_leave進(jìn)行平衡。
     *可以使用此功能將塊與多個(gè)組同時(shí)關(guān)聯(lián)。
     */
    dispatch_async(current2, ^{
        //表示已進(jìn)入組,是區(qū)別于dispatch_group_async的另一種加入組的方式
        dispatch_group_enter(group);
        NSLog(@"group任務(wù)下載圖片4---currentThread%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

控制臺(tái)

2017-10-26 17:21:12.387513+0800 GCD[32025:2599998] group任務(wù)下載圖片1---currentThread<NSThread: 0x604000272640>{number = 3, name = (null)}
2017-10-26 17:21:12.387619+0800 GCD[32025:2599996] group任務(wù)下載圖片4---currentThread<NSThread: 0x600000467a00>{number = 6, name = (null)}
2017-10-26 17:21:12.387624+0800 GCD[32025:2600001] group任務(wù)下載圖片3---currentThread<NSThread: 0x600000467980>{number = 5, name = (null)}
2017-10-26 17:21:12.387649+0800 GCD[32025:2599995] group任務(wù)下載圖片2---currentThread<NSThread: 0x604000272780>{number = 4, name = (null)}
2017-10-26 17:21:12.394213+0800 GCD[32025:2599907] 圖片下載完成,主線程展示

注釋掉

  dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"圖片下載完成,主線程展示");
    });

使用dispatch_group_wait等待執(zhí)行結(jié)束

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"圖片下載完成,主線程展示");

控制臺(tái)結(jié)果

2017-10-26 17:25:43.719035+0800 GCD[32080:2606880] group任務(wù)下載圖片3---currentThread<NSThread: 0x60000027df80>{number = 5, name = (null)}
2017-10-26 17:25:43.719053+0800 GCD[32080:2606878] group任務(wù)下載圖片1---currentThread<NSThread: 0x604000265140>{number = 3, name = (null)}
2017-10-26 17:25:43.719035+0800 GCD[32080:2606879] group任務(wù)下載圖片2---currentThread<NSThread: 0x60000027e3c0>{number = 4, name = (null)}
2017-10-26 17:25:43.719265+0800 GCD[32080:2606733] 圖片下載完成,主線程展示
2017-10-26 17:25:43.719820+0800 GCD[32080:2606879] group任務(wù)下載圖片4---currentThread<NSThread: 0x60000027e3c0>{number = 4, name = (null)}

6、dispatch_apply
將一個(gè)塊提交給調(diào)度隊(duì)列進(jìn)行多次調(diào)用,類似for循環(huán),但是dispatch_apply會(huì)等到所有block完成才返回。
官方說:將此函數(shù)與并發(fā)隊(duì)列一起使用可以作為一個(gè)高效的并行循環(huán)。
【應(yīng)用場景】用于混合應(yīng)用根據(jù)資源列表下載資源,配合dispatch_group_enter使用,實(shí)現(xiàn)資源下載成功之后,上傳下載結(jié)果。

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

iterations:要執(zhí)行的迭代次數(shù)。
queue:調(diào)度隊(duì)列
block:執(zhí)行任務(wù)的block
size_t:typedef typeof (sizeof(int)) size_t;

   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   NSArray * arr = @[@"貝加爾湖畔",@"十點(diǎn)半的地鐵",@"當(dāng)你老了",@"春風(fēng)十里不如你",@"風(fēng)吹麥浪",@"父親的散文詩"];
   // 第二個(gè)參數(shù)queue,可以是并行隊(duì)列,推薦DISPATCH_APPLY_AUTO,會(huì)自動(dòng)選擇一個(gè)適合調(diào)用線程的隊(duì)列。
   dispatch_apply(arr.count, queue, ^(size_t i) {
        NSLog(@"dispatch_apply %zu %@  currentThread %@",i,arr[i],[NSThread currentThread]);
   });

控制臺(tái)

2017-10-27 10:30:32.752105+0800 GCD[34170:3051371] dispatch_apply 0 貝加爾湖畔  currentThread <NSThread: 0x604000267e00>{number = 3, name = (null)}
2017-10-27 10:30:32.752101+0800 GCD[34170:3051273] dispatch_apply 3 春風(fēng)十里不如你  currentThread <NSThread: 0x6040000712c0>{number = 1, name = main}
2017-10-27 10:30:32.752105+0800 GCD[34170:3051372] dispatch_apply 2 當(dāng)你老了  currentThread <NSThread: 0x6000004672c0>{number = 5, name = (null)}
2017-10-27 10:30:32.752107+0800 GCD[34170:3051370] dispatch_apply 1 十點(diǎn)半的地鐵  currentThread <NSThread: 0x600000467b80>{number = 4, name = (null)}
2017-10-27 10:30:32.752339+0800 GCD[34170:3051273] dispatch_apply 5 父親的散文詩  currentThread <NSThread: 0x6040000712c0>{number = 1, name = main}
2017-10-27 10:30:32.752338+0800 GCD[34170:3051371] dispatch_apply 4 風(fēng)吹麥浪  currentThread <NSThread: 0x604000267e00>{number = 3, name = (null)}

7、dispatch_source_create
創(chuàng)建新的調(diào)度資源以監(jiān)聽系統(tǒng)的底層對象,并自動(dòng)將block提交給調(diào)度隊(duì)列以響應(yīng)事件。

dispatch_source_t dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue);

type:dispatch source的類型,type可用的值
handle:根據(jù)type不同,值不同
mask:根據(jù)type不同,值不同

type 用途 handle的值 mask的值
DISPATCH_SOURCE_TYPE_DATA_ADD 數(shù)據(jù)相加 0 0
DISPATCH_SOURCE_TYPE_DATA_OR 按位OR合并數(shù)據(jù) 0 0
DISPATCH_SOURCE_TYPE_MACH_RECV Mach端口獲取消息 mach_port_t 0
DISPATCH_SOURCE_TYPE_MACH_SEND Mach端口發(fā)送消息 mach_port_t Dispatch Source Mach Send Event Flags
DISPATCH_SOURCE_TYPE_PROC 事件進(jìn)程 pid_t Dispatch Source Process Event Flags.
DISPATCH_SOURCE_TYPE_READ 文件可讀 文件描述符(int) 0
DISPATCH_SOURCE_TYPE_SIGNAL 信號(hào)進(jìn)程 信號(hào)(int) 0
DISPATCH_SOURCE_TYPE_TIMER 定時(shí)器 0 0
DISPATCH_SOURCE_TYPE_VNODE 文件系統(tǒng)的變化 文件描述符(int) Dispatch Source Vnode Event Flags
DISPATCH_SOURCE_TYPE_WRITE 文件可寫 文件描述符(int) 0
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 系統(tǒng)內(nèi)存情況 0 FOO
void dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway);

可以在同一個(gè)調(diào)度定時(shí)器源對象上多次調(diào)用dispatch_source_set_timer方法,根據(jù)需要重置定時(shí)器源的時(shí)間間隔。
啟動(dòng)時(shí)間參數(shù)還決定了哪個(gè)時(shí)鐘用于定時(shí)器。 如果開始時(shí)間為DISPATCH_TIME_NOW或使用dispatch_time創(chuàng)建,則定時(shí)器基于mach_absolute_time。 否則,如果使用dispatch_walltime創(chuàng)建定時(shí)器的開始時(shí)間,則定時(shí)器基于gettimeofday(3)。
如果定時(shí)器源已被取消,則調(diào)用此功能不起作用。
start:定時(shí)開始的時(shí)間
interval:納秒級(jí)別的時(shí)間間隔,DISPATCH_TIME_FOREVER表示一次性定時(shí)器
leeway:納秒級(jí)別允許的延遲,也就是要求計(jì)時(shí)器觸發(fā)的精準(zhǔn)程度。
【解釋leeway】如果每5秒就要求觸發(fā)一次,對精度要求高,那么可以傳0。如果是一個(gè)周期性任務(wù),比如每15分鐘查詢一下郵件,對時(shí)間要求不需要很精確,那么可以傳60s,告訴系統(tǒng)60秒的誤差是可以接受的。這樣系統(tǒng)就可以聯(lián)合其他任務(wù)一起執(zhí)行,不用頻繁的切換線程,從而降低系統(tǒng)功耗,提高系統(tǒng)性能。(線程切換有系統(tǒng)開銷)
【注意】對于所有定時(shí)器,即使指定了leeway為零,也會(huì)有一些延遲。

void dispatch_source_cancel(dispatch_source_t source);

阻止處理事件的handler的繼續(xù)調(diào)用,但不會(huì)中斷已經(jīng)在進(jìn)行中的block任務(wù)。當(dāng)事件完成后,cancellation handler被提交到目標(biāo)隊(duì)列。cancellation handler在系統(tǒng)釋放了全部底層系統(tǒng)對象(文件描述符或mach端口)的引用之后,才提交給dispatch_source的目標(biāo)隊(duì)列。因此,cancellation handler是關(guān)閉或釋放此類系統(tǒng)對象的方便位置。但是在調(diào)用cancellation handler之前添加的關(guān)閉文件描述符或是回收mach port 的任務(wù)是無效的。

GCD定時(shí)器 :


#import "TestViewController.h"

@interface TestViewController ()
@property (nonatomic, strong) dispatch_source_t timer;
@property (nonatomic, assign) BOOL isSuspend;//是否是掛起狀態(tài)
@end

@implementation TestViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];

    [self creatTimer];
    typeof(self) weakSelf = self;
    //系統(tǒng)釋放了全部底層系統(tǒng)對象的引用之后,block會(huì)執(zhí)行
    dispatch_source_set_cancel_handler(self.timer, ^{
        NSLog(@"定時(shí)器銷毀了%@",weakSelf.timer);
    });
}


//創(chuàng)建定時(shí)器?
- (void)creatTimer{
    if (!self.timer) {
        self.isSuspend = NO;
        //定時(shí)器
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
        dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
        //事件處理handler
        dispatch_source_set_event_handler(timer, ^{
            NSLog(@"定時(shí)任務(wù)");
        });
        //啟動(dòng)
        /**第一次建議使用dispatch_activate,因?yàn)橄駋ueues and sources這樣的dispatch對象可能
         *inactive狀態(tài)被創(chuàng)建
         */
        dispatch_activate(timer);

        //創(chuàng)建一個(gè)屬性,timer需要強(qiáng)引用,局部變量被銷毀之后,定時(shí)操作就不起作用了。
        self.timer = timer;
    }
    
}

//啟動(dòng)??
- (IBAction)resumeTimer{
    if (self.timer&&self.isSuspend) {
        self.isSuspend = NO;
        //如果suspension count計(jì)數(shù)器為0,并且是非inactive狀態(tài),調(diào)用這個(gè)方法會(huì)觸發(fā)斷言終止進(jìn)程
        dispatch_resume(self.timer);
        NSLog(@"調(diào)用dispatch_resume 喚起線程");
    }
}
//暫停?
- (IBAction)suspendTimer{
    if (self.timer) {
        self.isSuspend = YES;
        //掛起的對象不會(huì)繼續(xù)調(diào)用任何一個(gè)關(guān)聯(lián)的block,但是不會(huì)中斷正在執(zhí)行的block
        dispatch_suspend(self.timer);
        NSLog(@"調(diào)用dispatch_suspend 掛起線程");
    }
}

//停止?
- (IBAction)stopTimer{
    if (self.timer) {
        //EXC_BAD_INSTRUCTION
        //如果是掛起狀態(tài) 調(diào)用dispatch_source_cancel 崩潰
        //dispatch_suspend調(diào)用后 timer 是無法被釋放的,一般情況下會(huì)發(fā)生崩潰。
        //這是因?yàn)閐ispatch source release 的時(shí)候判斷了當(dāng)前是否是在暫停狀態(tài)。
        if (self.isSuspend) {
            dispatch_resume(self.timer);
        }
        self.isSuspend = NO;
        dispatch_source_cancel(self.timer);
        self.timer = nil;
        NSLog(@"調(diào)用dispatch_source_cancel 銷毀資源");
    }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)dealloc{

    NSLog(@"銷毀了");
}

@end

【注意】

  • dispatch_resume與dispatch_suspend必須成對使用,但是沒有 API 獲取當(dāng)前是掛起還是執(zhí)行狀態(tài),所以需要自己記錄。
  • 如果suspension count計(jì)數(shù)器為0,并且是非inactive狀態(tài),調(diào)用dispatch_resume方法會(huì)觸發(fā)斷言終止進(jìn)程

【與NSTimer比較】

  • GCD timer不依賴RunLoop,更準(zhǔn)時(shí)。
  • [NSTimer scheduledTimerWithTimeInterval:time target:self selector:@selector(refresh) userInfo:nil repeats:YES]; repeats為YES時(shí),self引用計(jì)數(shù)+1,因此可能導(dǎo)致VC不釋放。
  • NSTimer的創(chuàng)建、銷毀必須在統(tǒng)一線程、performSelector的創(chuàng)建與撤銷必須在同一個(gè)線程操作。
  • 不論哪種定時(shí)器,不用的時(shí)候一定要銷毀。

結(jié)束語:GCD還有很多很多使用的API,建議大家多看看官方文檔
暫時(shí)就這么多,等有時(shí)間再更新更多的用法。

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

相關(guān)閱讀更多精彩內(nèi)容

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