(1) GCD基本概念
01 GCD
1.1 全稱是Grand Central Dispatch喳魏,可譯為“牛逼的中樞調(diào)度器”
純C語言,提供了非常多強(qiáng)大的函數(shù)
1.2 GCD的優(yōu)勢
GCD是蘋果公司為多核的并行運(yùn)算提出的解決方案送火,自動利用更多的CPU內(nèi)核(比如雙核誉简、四核)
GCD會自動管理線程的生命周期(創(chuàng)建線程喘先、調(diào)度任務(wù)钾恢、銷毀線程)
程序員只需要告訴GCD想要執(zhí)行什么任務(wù)翘鸭,不需要編寫任何管理線程的代碼
02 兩個核心概念-隊(duì)列和任務(wù)
2.1 任務(wù):需要執(zhí)行的操作;任務(wù)是最小單位滴铅,任務(wù)一旦執(zhí)行,就必須執(zhí)行完畢就乓;線程不同汉匙,線程可以暫停拱烁,阻塞,強(qiáng)制死亡噩翠。
2.2 隊(duì)列:用來存放任務(wù)戏自,任務(wù)的取出遵循隊(duì)列的FIFO原則:先進(jìn)先出,后進(jìn)后出
2.3 任務(wù)伤锚、隊(duì)列浦妄、線程、同異步函數(shù)的關(guān)系:
2.3.1任務(wù):一個具體的任務(wù)(操作)
2.3.2隊(duì)列:任務(wù)列表及執(zhí)行順序(并發(fā)隊(duì)列:無序執(zhí)行见芹;串行隊(duì)列:順序執(zhí)行)
2.3.3線程:車間工人剂娄,執(zhí)行任務(wù)的個人(實(shí)際由CPU處理)
2.3.4同步與異步函數(shù):決定執(zhí)行任務(wù)的總?cè)藬?shù)
2.3.5總結(jié):按什么順序由多少人(線程)執(zhí)行多少任務(wù)。
03 同步和異步(影響線程數(shù))
3.1 同步:只能在當(dāng)前線程中執(zhí)行任務(wù)玄呛,不具備開啟新線程的能力;在當(dāng)前線程執(zhí)行并且在所在隊(duì)列中馬上執(zhí)行(重要)
3.2 異步:可以在新的線程中執(zhí)行任務(wù)阅懦,具備開啟新線程的能力(具備不代表一定開啟新的線程),不要求馬上執(zhí)行
04 GCD的隊(duì)列類型(影響執(zhí)行順序)
4.1 并發(fā)隊(duì)列(Concurrent Dispatch Queue)
4.1.1 自動開啟多個線程同時(shí)執(zhí)行任務(wù)
4.1.2 并發(fā)功能只有在異步函數(shù)下才有效
4.2 串行隊(duì)列(Serial Dispatch Queue)
4.2.1 一個任務(wù)執(zhí)行完畢后徘铝,再執(zhí)行下一個任務(wù)
4.3 主隊(duì)列(跟主線程相關(guān)聯(lián)的隊(duì)列)
4.3.1 主隊(duì)列是GCD自帶的一種特殊的串行隊(duì)列
4.3.2 在主隊(duì)列中的任務(wù)耳胎,都會在主線程中執(zhí)行
4.3.3 主線程主隊(duì)列執(zhí)行的任務(wù)中存在同步函數(shù)+主隊(duì)列任務(wù),會導(dǎo)致死鎖(因?yàn)橹麝?duì)列中的當(dāng)前任務(wù)并沒有完成惕它,它的下一個任務(wù)是新增的同步函數(shù)+主隊(duì)列任務(wù)怕午,并且要求所在線程立即執(zhí)行所在隊(duì)列(所在隊(duì)列不是當(dāng)前隊(duì)列),系統(tǒng)無法在隊(duì)列中的當(dāng)前任務(wù)沒處理完成前切換任務(wù)淹魄,導(dǎo)致死鎖)
05 線程執(zhí)行組合:
01 異步函數(shù)+并發(fā)隊(duì)列:開啟多條線程郁惜,并發(fā)執(zhí)行任務(wù)
02 異步函數(shù)+串行隊(duì)列:開啟一條線程,串行執(zhí)行任務(wù)
03 同步函數(shù)+并發(fā)隊(duì)列:不開線程甲锡,串行執(zhí)行任務(wù)
04 同步函數(shù)+串行隊(duì)列:不開線程兆蕉,串行執(zhí)行任務(wù)
05 異步函數(shù)+主隊(duì)列:不開線程,在主線程中串行執(zhí)行任務(wù)
06 同步函數(shù)+主隊(duì)列:不開線程缤沦,在主線程中串行執(zhí)行任務(wù)(注意死鎖發(fā)生)
07 使用sync函數(shù)往當(dāng)前串行隊(duì)列中添加任務(wù)虎韵,會卡住當(dāng)前的串行隊(duì)列
(2)GCD基本使用【重點(diǎn)】
- 步驟(以什么順序由多少人(線程)執(zhí)行多少任務(wù)):
創(chuàng)建或獲取隊(duì)列
通過同步函數(shù)或異步函數(shù)或函數(shù)方式將任務(wù)添加到隊(duì)列中
01 同步函數(shù)與異步函數(shù)
1.1 用同步(sync)的函數(shù)執(zhí)行任務(wù)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:隊(duì)列
block:任務(wù)
1.2 用異步(async)的函數(shù)執(zhí)行任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
queue:隊(duì)列
block:任務(wù)
1.3 通過函數(shù)的方式來封裝任務(wù)
/*
第一個參數(shù):隊(duì)列
第二個參數(shù):要調(diào)用函數(shù)需要傳遞的參數(shù)
第三個參數(shù):函數(shù)
*/
NSString *str = @"wendingding";
//橋接--(__)
dispatch_async_f(queue, (__bridge void *)(str), run);
02 并發(fā)隊(duì)列與串行隊(duì)列
2.1 手動創(chuàng)建并發(fā)隊(duì)列
/*
第一個參數(shù):C語言的字符串 隊(duì)列名稱
第二個參數(shù):隊(duì)列的類型
DISPATCH_QUEUE_CONCURRENT--并發(fā)
DISPATCH_QUEUE_SERIAL---串行隊(duì)列
*/
dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_CONCURRENT);
2.2 獲得全局并發(fā)隊(duì)列
2.2.1GCD默認(rèn)已經(jīng)提供了全局的并發(fā)隊(duì)列,供整個應(yīng)用使用缸废,可以無需手動創(chuàng)建
/*
第一個參數(shù):隊(duì)列的優(yōu)先級
第二個參數(shù):此參數(shù)是留給未來使用包蓝,暫時(shí)無用,用0即可
DISPATCH_QUEUE_CONCURRENT--并發(fā)
DISPATCH_QUEUE_SERIAL---串行隊(duì)列
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2.2.2 全局并發(fā)隊(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 // 后臺
2.3 串行隊(duì)列
/*
第一個參數(shù):C語言的字符串 隊(duì)列名稱
第二個參數(shù):隊(duì)列的類型
DISPATCH_QUEUE_CONCURRENT--并發(fā)
DISPATCH_QUEUE_SERIAL---串行隊(duì)列或傳遞NULL
*/
dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL);
2.4 使用主隊(duì)列(跟主線程相關(guān)聯(lián)的隊(duì)列)
//使用dispatch_get_main_queue()獲得主隊(duì)列
dispatch_queue_t queue = dispatch_get_main_queue();
(3)GCD線程間通信
//0.獲取一個全局的隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//1.先開啟一個線程企量,把下載圖片的操作放在子線程中處理
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]);
});
});
(4)GCD其它常用函數(shù)
01 柵欄函數(shù)(控制任務(wù)的執(zhí)行順序)
dispatch_barrier_sync(queue, ^{
NSLog(@"--dispatch_barrier_sync-");
});
//在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行测萎,而且它后面的任務(wù)等它執(zhí)行完成之后才會執(zhí)行
//這個queue不能是全局的并發(fā)隊(duì)列,蘋果文檔沒做解釋
02 延遲執(zhí)行(延遲·控制在哪個線程執(zhí)行)
2.1 第一種方法——使用GCD函數(shù)
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]);
});
2.2 第二種方法——調(diào)用NSObject的方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后再調(diào)用self的run方法
2.3 第三種方法——使用NSTimer
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];
03 一次性代碼(注意不能用在懶加載)
-(void)once
{
//整個程序運(yùn)行過程中只執(zhí)行一次梁钾,默認(rèn)是線程安全的
//onceToken用來記錄該部分的代碼是否被執(zhí)行過
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"-----");
});
}
04 快速迭代(開多個線程并發(fā)完成迭代操作)
dispatch_apply(subpaths.count, queue, ^(size_t index) {
// index順序不確定
// 內(nèi)部開多個線程并發(fā)完成迭代操作绳泉,有可能包括主線程
// 雖然是多線程,但一樣會卡住線程
// 應(yīng)用場景:快速處理某些不算太耗時(shí)的操作姆泻,并回到主線程進(jìn)行某些操作零酪。
// 如果處理耗時(shí)操作冒嫡,建議使用并發(fā)+異步函數(shù),手動回到主線程刷新四苇,不建議使用這種方法
});
05 隊(duì)列組(同柵欄函數(shù))
//創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//多次執(zhí)行耗時(shí)的異步操作
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執(zhí)行1個耗時(shí)的異步操作
});
//隊(duì)列組中的任務(wù)執(zhí)行完畢之后孝凌,執(zhí)行該函數(shù)
dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
(5)快速迭代剪切文件
-(void)apply
{
//1.創(chuàng)建文件管理者
NSFileManager *manager = [NSFileManager defaultManager];
NSString *from = @"/Users/a1/Desktop/form";
NSString *to = @"/Users/a1/Desktop/to";
//2.獲得要剪切的所有文件
NSArray *subPaths =[manager subpathsAtPath:from];
NSLog(@"%@",subPaths);
NSInteger count = subPaths.count;
//3.創(chuàng)建并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 4.快速迭代
// 內(nèi)部不能使用continue
/*
第一個參數(shù):循環(huán)的次數(shù)
第二個參數(shù):隊(duì)列
第三個參數(shù):block 在里面執(zhí)行迭代任務(wù) index:索引
*/
dispatch_apply(count, queue, ^(size_t index) {
//剪切圖片
//4.1獲得文件名稱
NSString *fileName = subPaths[index];
//4.2 拼接要剪切的文件的全路徑
NSString *fullPath1 = [from stringByAppendingPathComponent:fileName];
//4.3 拼接文件要剪切到哪個地方的全路徑
NSString *fullpath2 = [to stringByAppendingPathComponent:fileName];
//4.4 執(zhí)行剪切
[manager moveItemAtPath:fullPath1 toPath:fullpath2 error:nil];
NSLog(@"%@---%@---%@",[NSThread currentThread],fullPath1,fullpath2);
});
}
(6)GDC其他內(nèi)容
01 使用Crearte函數(shù)創(chuàng)建的并發(fā)隊(duì)列和全局并發(fā)隊(duì)列的主要區(qū)別:
1. 全局并發(fā)隊(duì)列在整個應(yīng)用程序中本身是默認(rèn)存在的,并且對應(yīng)有高優(yōu)先級月腋、默認(rèn)優(yōu)先級蟀架、低優(yōu)先級和后臺優(yōu)先級一共四個并發(fā)隊(duì)列,我們只是選擇其中的一個直接拿來用榆骚。
2. Crearte函數(shù)是實(shí)打?qū)嵉膹念^開始去創(chuàng)建一個隊(duì)列片拍。
02 GCD內(nèi)存管理
1. 在iOS6.0之前,在GCD中凡是使用了帶Crearte和retain的函數(shù)在最后都需要做一次release操作妓肢。而主隊(duì)列和全局并發(fā)隊(duì)列不需要我們手動release捌省。
2. 在iOS6.0之后GCD已經(jīng)被納入到了ARC的內(nèi)存管理范疇中,即便是使用retain或者create函數(shù)創(chuàng)建的對象也不再需要開發(fā)人員手動釋放碉钠,我們像對待普通OC對象一樣對待GCD就OK纲缓。
03 在使用柵欄函數(shù)的時(shí)候,蘋果官方明確規(guī)定柵欄函數(shù)只有在和使用create函數(shù)自己的創(chuàng)建的并發(fā)隊(duì)列一起使用的時(shí)候才有效(沒有給出具體原因)
04 其它區(qū)別涉及到XNU內(nèi)核的系統(tǒng)級線程編程喊废,不一一列舉
05 給出一些參考資料(可以自行研究):
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/