一、什么是GCD
全稱(chēng)是Grand Central Dispatch,可譯為“牛逼的中樞調(diào)度器”,純C語(yǔ)言,提供了非常多強(qiáng)大的函數(shù)
二、GCD的優(yōu)勢(shì)
GCD是蘋(píng)果公司為多核的并行運(yùn)算提出的解決方案
GCD會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核)
GCD會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷(xiāo)毀線程)
程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫(xiě)任何線程管理代碼
三、GCD的概念
1)基本概念
????????1、任務(wù)和隊(duì)列?:任務(wù):執(zhí)行什么操作? ? ? ? 隊(duì)列:用來(lái)存放任務(wù)
????????2、同步和異步:同步:只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開(kāi)啟新線程的能力? ? ? ?異步:可以在新的線程中執(zhí)行任務(wù),具備開(kāi)啟新線程的能力
????????3、并發(fā)和串行:并發(fā):多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行? ? ??串行:一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)
????????4、進(jìn)程和線程:進(jìn)程:一個(gè)可執(zhí)行的程序(一個(gè)app就是一個(gè)線程)? ? 線程:程序中一個(gè)單一的順序控制流程
2)區(qū)別及原因
? ? ? ? 1、我們用GCD無(wú)非就是想合理完成任務(wù),那么我們會(huì)面臨2個(gè)重要步驟:一、知道想做的事情——定制任務(wù);二、任務(wù)很多怎么辦?讓它們不要亂排個(gè)隊(duì)——將任務(wù)添加到隊(duì)列中(GCD會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出,放到對(duì)應(yīng)的線程中執(zhí)行,任務(wù)的取出遵循隊(duì)列的FIFO原則:先進(jìn)先出,后進(jìn)后出)。
? ? ? ? 2、多任務(wù)會(huì)添加進(jìn)隊(duì)列,隊(duì)列有區(qū)分兩種——并發(fā)(行)和串行隊(duì)列
? ? ? ? ? ? 并發(fā)隊(duì)列(Concurrent Dispatch Queue):可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動(dòng)開(kāi)啟多個(gè)線程同時(shí)執(zhí)行任務(wù))
????????????串行隊(duì)列(Serial Dispatch Queue):讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))
? ?????????并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
? ? ? ?3、任務(wù)添加好了那么就要開(kāi)始執(zhí)行任務(wù),GCD任務(wù)執(zhí)行有兩種方式——同步與異步
? ? ? ? 同步——發(fā)出一個(gè)調(diào)用時(shí),在沒(méi)有得到結(jié)果之前,該調(diào)用就不返回。但是一旦調(diào)用返回,就得到返回值了。換句話說(shuō),就是由*調(diào)用者*主動(dòng)等待這個(gè)*調(diào)用*的結(jié)果。異步——在發(fā)出之后,這個(gè)調(diào)用就直接返回了,所以沒(méi)有返回結(jié)果。
具體理解:有A,B,C三個(gè)任務(wù),同步執(zhí)行——程序?qū)⒌却鼳 執(zhí)行完畢之后, 再執(zhí)行B, 再執(zhí)行C;異步執(zhí)行——程序直接跳過(guò)A,B,C,執(zhí)行后面的代碼, 執(zhí)行完畢之后, 再來(lái)執(zhí)行A,B,C中的任務(wù)
同步和異步主要影響:能不能開(kāi)啟新的線程? ??同步:在當(dāng)前線程中執(zhí)行任務(wù),不具備開(kāi)啟新線程的能力? ? 異步:在新的線程中執(zhí)行任務(wù),具備開(kāi)啟新線程的能力。
四、GCD的基本使用
? ? ? ? 1、串行隊(duì)列:系統(tǒng)提供(主隊(duì)列):dispatch_queue_t q = dispatch_get_main_queue(); 自己創(chuàng)建:dispatch_queue_tqueue =dispatch_queue_create("cn.itcast.queue",NULL);//第一個(gè)參數(shù)為隊(duì)列名
? ? ? ? 2、并行隊(duì)列:系統(tǒng)提供(全局隊(duì)列):dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);(里面的參數(shù)第一個(gè)為優(yōu)先級(jí),可以默認(rèn)為固定寫(xiě)法)
全局并發(fā)隊(duì)列的優(yōu)先級(jí)
#define DISPATCH_QUEUE_PRIORITY_HIGH2?高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT0?默認(rèn)(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)?低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN?后臺(tái)?
自己創(chuàng)建:dispatch_queue_t q =dispatch_queue_create("......",DISPATCH_QUEUE_CONCURRENT);
? ? ? ? ?3、 同步操作:dispatch_sync?會(huì)依次順序執(zhí)行,能夠決定任務(wù)的執(zhí)行順序;
? ? ? ? ?4、?異步操作:dispatch_async會(huì)并發(fā)執(zhí)行,無(wú)法確定任務(wù)的執(zhí)行順序;
? ? ? ? ?5、是否開(kāi)啟線程
????????????01、異步函數(shù)+并發(fā)隊(duì)列:開(kāi)啟多條線程,并發(fā)執(zhí)行任務(wù)
????????????02、異步函數(shù)+串行隊(duì)列:開(kāi)啟一條線程,串行執(zhí)行任務(wù)
????????????03、同步函數(shù)+并發(fā)隊(duì)列:不開(kāi)線程,串行執(zhí)行任務(wù)
????????????04、同步函數(shù)+串行隊(duì)列:不開(kāi)線程,串行執(zhí)行任務(wù)
????????????05、異步函數(shù)+主隊(duì)列:不開(kāi)線程,在主線程中串行執(zhí)行任務(wù)
????????????06、同步函數(shù)+主隊(duì)列:不開(kāi)線程,串行執(zhí)行任務(wù)(注意死鎖發(fā)生)
????????????07、注意同步函數(shù)和異步函數(shù)在執(zhí)行順序上面的差異
?????????6、下載圖片操作實(shí)例:
? ? ? ? ? ? ? ?0、獲取一個(gè)全局的隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
? ? ? ? ? ? ? ?1、先開(kāi)啟一個(gè)線程,把下載圖片的操作放在子線程中處理dispatch_async(queue, ^{
???????????????2、下載圖片
NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c3320b14bb3184c510fd8f9a185.jpg"];
? ? NSData *data = [NSData dataWithContentsOfURL:url];
??? UIImage *image = [UIImage imageWithData:data];
??? NSLog(@"下載操作所在的線程--%@",[NSThread currentThread]);
? ? ? ? ? ? ? ?3、回到主線程刷新UI
??? dispatch_async(dispatch_get_main_queue(), ^{
???????????self.imageView.image = image;
???????????打印查看當(dāng)前線程
? ? ? ? ? ?NSLog(@"刷新UI---%@",[NSThread currentThread]);
??? });
});
?GCD其它常用函數(shù)
????????01、柵欄函數(shù)(控制任務(wù)的執(zhí)行順序)
dispatch_barrier_async(queue, ^{
? NSLog(@"--dispatch_barrier_async-");
});
? ??????02、延遲執(zhí)行(延遲·控制在哪個(gè)線程執(zhí)行)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
??? NSLog(@"---%@",[NSThread currentThread]);
});
? ??????03、一次性代碼(注意不能放到懶加載)
-(void)once
{
???整個(gè)程序運(yùn)行過(guò)程中只會(huì)執(zhí)行一次
???onceToken用來(lái)記錄該部分的代碼是否被執(zhí)行過(guò)
???staticdispatch_once_t onceToken;
? ?dispatch_once(&onceToken, ^{
??????? NSLog(@"-----");
??? });
}
? ??????04、快速迭代(開(kāi)多個(gè)線程并發(fā)完成迭代操作,可能會(huì)提高一定的性能)
dispatch_apply(subpaths.count, queue, ^(size_t index) {
});
? ??????05、隊(duì)列組(同柵欄函數(shù))
創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
隊(duì)列組中的任務(wù)執(zhí)行完畢之后,執(zhí)行該函數(shù)
dispatch_group_notify(dispatch_group_t group,
????????????????????? dispatch_queue_t queue,
????????????????????? dispatch_block_t block);
代碼案例:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
? ? [selfsyncWithMain];
}
同步函數(shù)+主隊(duì)列:死鎖
-(void)syncWithMain
{
??? NSLog(@"-----");
???1.獲得主隊(duì)列
??? dispatch_queue_t queue = dispatch_get_main_queue();
??? dispatch_sync(queue, ^{
??????? NSLog(@"1---%@",[NSThread currentThread]);
??? });? ?
}
異步函數(shù)+主隊(duì)列:不會(huì)開(kāi)線程,串行執(zhí)行
凡是在主隊(duì)列中的任務(wù)都在主線程中去執(zhí)行
-(void)asyncWithMain
{
???1.獲得主隊(duì)列
??? dispatch_queue_t queue = dispatch_get_main_queue();
???異步函數(shù)
??? dispatch_async(queue, ^{
??????? NSLog(@"1---%@",[NSThread currentThread]);
??? });
}
同步函數(shù)+并發(fā)隊(duì)列:不會(huì)開(kāi)線程,串行執(zhí)行
-(void)syncWithConcuerrent
{
???獲得全局并發(fā)隊(duì)列
??? dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
???同步函數(shù)
??? dispatch_sync(queue, ^{
??????? NSLog(@"1---%@",[NSThread currentThread]);
??? });
}
同步函數(shù)+串行隊(duì)列:不會(huì)開(kāi)線程,串行執(zhí)行
-(void)syncWithserial
{
???1.創(chuàng)建隊(duì)列(串行隊(duì)列)
??? dispatch_queue_t queue = dispatch_queue_create("com.520it.download", DISPATCH_QUEUE_SERIAL);
???同步函數(shù)
??? dispatch_sync(queue, ^{
??????? NSLog(@"1---%@",[NSThread currentThread]);
??? });
}
異步函數(shù)+串行隊(duì)列:會(huì)開(kāi)1條線程,串行執(zhí)行
-(void)asyncWithserial
{
???1.創(chuàng)建隊(duì)列(串行隊(duì)列)
??? dispatch_queue_t queue = dispatch_queue_create("com.520it.download", DISPATCH_QUEUE_SERIAL);
???異步函數(shù)
??? dispatch_async(queue, ^{
??????? NSLog(@"1---%@",[NSThread currentThread]);
??? });
}
異步函數(shù)+并發(fā)隊(duì)列:會(huì)開(kāi)線程,會(huì)開(kāi)啟多條線程,并發(fā)執(zhí)行
-(void)asyncWithConcurrent
{
???1.創(chuàng)建隊(duì)列(并發(fā)隊(duì)列)
????第一個(gè)參數(shù):C語(yǔ)言的字符傳,標(biāo)簽
????第二個(gè)參數(shù):隊(duì)列的類(lèi)型
??? dispatch_queue_t queue = dispatch_queue_create("com.520it.download", DISPATCH_QUEUE_CONCURRENT);
???異步函數(shù)
??? dispatch_async(queue, ^{
??????? NSLog(@"1---%@",[NSThread currentThread]);
??? });
}
各種隊(duì)列的執(zhí)行效果
注意
使用dispatch_sync函數(shù)往當(dāng)前串行隊(duì)列中添加任務(wù),會(huì)卡住當(dāng)前的串行隊(duì)列
五、線程間通信示例
? ? ? ? 1、從子線程回到主線程
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
?????執(zhí)行耗時(shí)的異步操作...
?????dispatch_async(dispatch_get_main_queue(), ^{
?????回到主線程,執(zhí)行UI刷新操作
??????? });
});
????????2、延時(shí)執(zhí)行:iOS常見(jiàn)的延時(shí)執(zhí)行有2種方式
?????????????注意:調(diào)用NSObject的方法(一旦定制好延遲任務(wù)后,不會(huì)卡主當(dāng)前線程:經(jīng)常使用)
? ? ? ? ? ? ? ? a.自動(dòng)回到當(dāng)前線程(在哪個(gè)線程調(diào)的就回到哪個(gè)線程)調(diào)用self的download方法,并且傳遞參數(shù)withObject:參數(shù)afterDelay:延遲的時(shí)間
[selfperformSelector:@selector(run)withObject:nilafterDelay:2.0];
設(shè)定好延遲的時(shí)間后,它會(huì)先執(zhí)行后邊的代碼,2秒后再調(diào)用self的run方法(并且不會(huì)卡主線程,在主線程調(diào)最后會(huì)回到主線程,在子線程調(diào)最后會(huì)回到子線程)
????????????????b.使用GCD函數(shù)(3秒后自動(dòng)開(kāi)啟新線程執(zhí)行block中的代碼,不會(huì)卡主當(dāng)前的線程,在主/子線程調(diào)用都可以使用)
? ??????????????dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0*NSEC_PER_SEC)),dispatch_get_main_queue(), ^{
???2秒后執(zhí)行這里的代碼...在哪個(gè)線程執(zhí)行,跟隊(duì)列類(lèi)型有關(guān)?
});
????????????????DISPATCH_TIME_NOW:現(xiàn)在開(kāi)始的意
????????????????2.0*NSEC_PER_SEC:設(shè)置的秒數(shù)(直接更改數(shù)字即可)
? ? ? ? ? ? ? ? ?dispatch_get_main_queue():主隊(duì)列的意思
????????????3.會(huì)卡主線程(不推薦使用)
????????????[NSThread sleepForTimeInterval:3]
? ??????3、一次性代碼
使用dispatch_once函數(shù)能保證某段代碼在程序運(yùn)行過(guò)程中只被執(zhí)行1次
staticdispatch_once_tonceToken;
dispatch_once(&onceToken, ^{
????程序運(yùn)行過(guò)程中,永遠(yuǎn)只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
});
? ??????4、隊(duì)列組
????????????????有這么1種需求:首先,分別異步執(zhí)行2個(gè)耗時(shí)的操作。其次,等2個(gè)異步操作都執(zhí)行完畢后,再回到主線程執(zhí)行操作
????????????????如果想要快速高效地實(shí)現(xiàn)上述需求,可以考慮用隊(duì)列組(先創(chuàng)建一個(gè)隊(duì)列組,再把要執(zhí)行的一個(gè)異步操作放入隊(duì)列組的block當(dāng)中)
????????????????dispatch_group_tgroup =?dispatch_group_create();
????????????????dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
? ? 執(zhí)行1個(gè)耗時(shí)的異步操作
});
????????????????dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
????執(zhí)行1個(gè)耗時(shí)的異步操作
});
????????????????dispatch_group_notify(group,dispatch_get_main_queue(), ^{
????????????等前面的異步操作都執(zhí)行完畢后,回到主線程...
});
注意:這個(gè)組的特點(diǎn),它會(huì)等組里面所有的任務(wù)(同上的兩個(gè)block代碼內(nèi)容)都執(zhí)行完了以后,它會(huì)調(diào)用_notify里面的這個(gè)block;?
六、補(bǔ)充
????????1、關(guān)于GCD中的創(chuàng)建和釋放
在iOS6.0之前,在GCD中每當(dāng)使用帶creat單詞的函數(shù)創(chuàng)建對(duì)象之后,都應(yīng)該對(duì)其進(jìn)行一次release操作。在iOS6.0之后,GCD被納入到了ARC的內(nèi)存管理機(jī)制中,在使用GCD的時(shí)候我們就像對(duì)待普通OC對(duì)象一樣對(duì)待GCD,因此不再需要我們調(diào)用release方法。
????????2、GCD中設(shè)置隊(duì)列的優(yōu)先級(jí)
????????????01使用create函數(shù)創(chuàng)建出來(lái)的隊(duì)列不論是串行隊(duì)列還是并發(fā)隊(duì)列,,其執(zhí)行任務(wù)線程的優(yōu)先級(jí)都是默認(rèn)優(yōu)先級(jí)。
????????????02可以通過(guò)set_target_queue來(lái)變更隊(duì)列的優(yōu)先級(jí)。第一個(gè)參數(shù)傳通過(guò)creat創(chuàng)建出來(lái)的隊(duì)列,后面一個(gè)參數(shù)傳指定了優(yōu)先級(jí)的全局并發(fā)隊(duì)列。第一個(gè)參數(shù)如果傳主隊(duì)列或者全局并發(fā)隊(duì)列的話,那么執(zhí)行結(jié)果是未知的。
????????3、暫停和恢復(fù)。
????????????????GCD中的隊(duì)列也是可以暫停和恢復(fù)的,直接把相應(yīng)的隊(duì)列作為參數(shù)就傳遞就可以。使用dispatch_resume(queue1);和dispatch_suspend(queue1);
????????4、 GCD中可以不使用block而使用函數(shù)。
????????5、在NSOperation中關(guān)于main方法的調(diào)用問(wèn)題。
????????????先調(diào)用start方法,在start方法內(nèi)部會(huì)調(diào)用main方法。可以通過(guò)代碼來(lái)進(jìn)行驗(yàn)證。
參考資料:https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/c/func/dispatch_queue_create
????????6、使用Crearte函數(shù)創(chuàng)建的并發(fā)隊(duì)列和全局并發(fā)隊(duì)列的主要區(qū)別:
? ? ? ? ? ? 1.1、全局并發(fā)隊(duì)列在整個(gè)應(yīng)用程序中本身是默認(rèn)存在的,并且對(duì)應(yīng)有高優(yōu)先級(jí)、默認(rèn)優(yōu)先級(jí)、低優(yōu)先級(jí)和后臺(tái)優(yōu)先級(jí)一共四個(gè)并發(fā)隊(duì)列,我們只是選擇其中的一個(gè)直接拿來(lái)用。而Crearte函數(shù)是實(shí)打?qū)嵉膹念^開(kāi)始去創(chuàng)建一個(gè)隊(duì)列。
? ? ? ? ? ? 1.2、在iOS6.0之前,在GCD中凡是使用了帶Crearte和retain的函數(shù)在最后都需要做一次release操作。而主隊(duì)列和全局并發(fā)隊(duì)列不需要我們手動(dòng)release。當(dāng)然了,在iOS6.0之后GCD已經(jīng)被納入到了ARC的內(nèi)存管理范疇中,即便是使用retain或者create函數(shù)創(chuàng)建的對(duì)象也不再需要開(kāi)發(fā)人員手動(dòng)釋放,我們像對(duì)待普通OC對(duì)象一樣對(duì)待GCD就OK。
????????????1.3、在使用柵欄函數(shù)的時(shí)候,蘋(píng)果官方明確規(guī)定柵欄函數(shù)只有在和使用create函數(shù)自己的創(chuàng)建的并發(fā)隊(duì)列一起使用的時(shí)候才有效(沒(méi)有給出具體原因)
????????????1.4、其它區(qū)別涉及到XUN內(nèi)核的系統(tǒng)級(jí)線程編程,不一一列舉。
????????????1.5、給出一些參考資料(可以自行研究):? ? GCDAPI:https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/c/func/dispatch_queue_create
Libdispatch版本源碼:http://www.opensource.apple.com/source/libdispatch/libdispatch-187.5/