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í)間再更新更多的用法。