一于樟、什么是 GCD
1. GCD 是蘋果為解決多線程而定義的一套庫阱洪,并且 GCD 可以自動管理線程的生命周期,就和 ARC 類似槽奕,不需要我們手動去管理
2. GCD 是用 純C 語言 寫的嘴纺,所以我門使用的是 GCD 中的函數(shù)败晴,并不是面向?qū)ο蟮姆椒?/h5>
3. GCD 核心概念
1)任務(wù) : 就是某個線程要執(zhí)行的方法
2)隊列 : 存放所有的任務(wù)
4. GCD 使用步驟
1)確定要執(zhí)行的任務(wù)
2)將任務(wù)添加到隊列中,GCD 會自動將隊列中的任務(wù)取出栽渴,放在對應(yīng)的線程中去執(zhí)行
5. 同步異步
1)同步 : 在同一個線程中執(zhí)行任務(wù)尖坤,不會創(chuàng)建新的線程
// 同步函數(shù)
// 參數(shù) 1: 隊列
// 參數(shù) 2: 任務(wù)的代碼塊
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
2)異步 : 創(chuàng)建一個新的線程,并在新的線程中執(zhí)行任務(wù)
// 異步函數(shù)
// 參數(shù) 1: 隊列
// 參數(shù) 2: 任務(wù)的代碼塊
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
6. 隊列
隊列可分為兩種
1)異步隊列 : 即并行執(zhí)行的隊列闲擦,隊列中的每個任務(wù)都可以并發(fā)(同步)執(zhí)行
2)串行隊列 : 即串行執(zhí)行的隊列慢味,隊列中的每個任務(wù)需要串行執(zhí)行,即一個一個來
獲得隊列
// 創(chuàng)建串行隊列
// 參數(shù) 1: 隊列名稱佛致,C風(fēng)格字符串
// 參數(shù) 2: 隊列的屬性贮缕,一般用 NULL 即可
dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
dispatch_queue_t 是 GCD 中隊列的類型
// 獲得主隊列辙谜,主隊列是一個串行隊列俺榆,并且與主線程對應(yīng),主隊列中的任務(wù)都會被主線程執(zhí)行 dispatch_queue_t dispatch_get_main_queue(void);
// 全局并發(fā)隊列装哆,可以供整個應(yīng)用使用罐脊,不需手動創(chuàng)建
// 參數(shù) 1: 隊列的優(yōu)先級(有4個)
// #define DISPATCH_QUEUE_PRIORITY_HIGH 2 高優(yōu)先級
// #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默認(rèn)
// #define DISPATCH_QUEUE_PRIORITY_LOW (-2) 低優(yōu)先級
// 參數(shù) 2: 隊列的屬性,可以穿 0
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
二蜕琴、GCD 基礎(chǔ)應(yīng)用
1. 異步/同步函數(shù) 與 串行/并行隊列
1)使用異步函數(shù)向并發(fā)隊列中添加任務(wù)
// 1. 打印主線程
NSLog(@"主線程 --- %@", [NSThread currentThread]);
// 2. 獲取全局并發(fā)隊列萍桌,并設(shè)置優(yōu)先級為默認(rèn)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 3. 添加任務(wù)到并行隊列中,就可以執(zhí)行任務(wù)了
// 使用異步函數(shù)添加任務(wù)凌简,可以開啟新的線程
dispatch_async(queue, ^{
NSLog(@"任務(wù) 1 --- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務(wù) 2 --- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務(wù) 3 --- %@", [NSThread currentThread]);
});
運行結(jié)果
總結(jié) : 可以看出上炎,除了主線程之外,還分別創(chuàng)建了三個子線程雏搂,并且三個子線程是并發(fā)執(zhí)行的
2)使用異步函數(shù)向串行隊列中添加任務(wù)
// 1. 創(chuàng)建串行隊列
// 參數(shù) 1: 串行隊列的名稱藕施,是 C風(fēng)格字符串
// 參數(shù) 2: 串行隊列的屬性,一般來說串行隊列是不需要任何屬性凸郑,可以傳 NULL
dispatch_queue_t queue = dispatch_queue_create("Chuanxin", NULL);
NSLog(@"主線程 --- %@", [NSThread currentThread]);
// 2. 使用異步函數(shù)往串行隊列中添加任務(wù)
dispatch_async(queue, ^{
NSLog(@"任務(wù) 1 --- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務(wù) 2 --- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務(wù) 3 --- %@", [NSThread currentThread]);
});
運行結(jié)果
總結(jié) : 使用異步函數(shù)向串行隊列中添加任務(wù)時裳食,會開啟新的線程,但是只會開啟一個芙沥;因為串行隊列中的任務(wù)需要一個一個執(zhí)行诲祸,不必同時執(zhí)行浊吏,所以只會創(chuàng)建一個新新線程
3)使用同步函數(shù)向并行隊列中添加任務(wù)
NSLog(@"主線程 --- %@", [NSThread currentThread]);
// 1. 獲取全局的并行隊列,并設(shè)置優(yōu)先級為 默認(rèn)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2. 使用同步函數(shù)往并行隊列中添加任務(wù)
dispatch_sync(queue, ^{
NSLog(@"任務(wù)1 --- %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務(wù)2 --- %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務(wù)3 --- %@", [NSThread currentThread]);
});
運行結(jié)果
總結(jié) : 因為使用的是同步函數(shù)救氯,所以不會創(chuàng)建新的線程找田,所以都是在主線程中執(zhí)行;此時径密,并發(fā)隊列就失去了其功能午阵,因為都沒有新的線程創(chuàng)建,何談并發(fā)
4)使用同步函數(shù)向串行隊列中添加任務(wù)
NSLog(@"主線程 --- %@", [NSThread currentThread]);
// 1. 創(chuàng)建串行隊列
dispatch_queue_t queue = dispatch_queue_create("Chuanxing", NULL);
// 2. 使用同步函數(shù)往串行隊列中添加任務(wù)
dispatch_sync(queue, ^{
NSLog(@"任務(wù)1 --- %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務(wù)2 --- %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務(wù)2 --- %@", [NSThread currentThread]);
});
運行結(jié)果
總結(jié) : 因為使用的是同步函數(shù)享扔,所以不會創(chuàng)建新線程底桂,所以都在主線程中執(zhí)行,并且是在串行隊列中惧眠,所以任務(wù)會一個一個執(zhí)行
2. 主隊列 與 同步/異步
1)使用異步函數(shù)向主隊列添加任務(wù)
// 1. 獲取主線程
NSLog(@"mainThread --- %@", [NSThread currentThread]);
// 1. 獲取主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 2. 使用異步函數(shù)向主隊列中添加任務(wù)
dispatch_async(mainQueue, ^{
NSLog(@"任務(wù) 1 --- %@", [NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"任務(wù) 2 --- %@", [NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"任務(wù) 3 --- %@", [NSThread currentThread]);
});
運行結(jié)果
總結(jié) : 雖然使用異步函數(shù)籽懦,但是卻向主隊列中添加任務(wù),所以不會創(chuàng)建新的線程氛魁,都在主隊列中執(zhí)行任務(wù)暮顺,并且由于主隊列是串行隊列,所以任務(wù)會一個一個執(zhí)行
2)使用同步函數(shù)向主隊列中添加任務(wù)
// 1. 獲取主線程
NSLog(@"mainThread --- %@", [NSThread currentThread]);
// 2. 獲取主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 3. 使用同步方式向主隊列中添加任務(wù)
dispatch_sync(mainQueue, ^{
NSLog(@"任務(wù) 1 --- %@", [NSThread currentThread]);
});
dispatch_sync(mainQueue, ^{
NSLog(@"任務(wù) 2 --- %@", [NSThread currentThread]);
});
dispatch_sync(mainQueue, ^{
NSLog(@"任務(wù) 2 --- %@", [NSThread currentThread]);
});
運行結(jié)果
使用同步函數(shù)向主隊列添加任務(wù)時會使程序崩潰
例如上述代碼秀存,當(dāng)把 “任務(wù)1” 添加到主隊列時捶码,主隊列變會讓主線程執(zhí)行該任務(wù),但是此時主線程正在執(zhí)行該同步函數(shù)或链,如此一來惫恼,便產(chǎn)生了一個死循環(huán),導(dǎo)致死鎖
3. 在子線程中創(chuàng)建子線程
- (void)test3 {
// 1. 獲取當(dāng)前線程(主線程)
NSLog(@"currentThread --- %@", [NSThread currentThread]);
// 2. 創(chuàng)建一個新的線程澳盐,并執(zhí)行指定方法
[self performSelectorInBackground:@selector(runInSubthread:) withObject:@"在子線程的子線程中執(zhí)行任務(wù)"];
}
- (void)runInSubthread:(NSString *)str {
// 1. 獲取當(dāng)前線程(子線程)
NSLog(@"currentThread --- %@ --- %@", [NSThread currentThread], str);
// 2. 獲取全局隊列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 3. 使用異步函數(shù)向全局隊列中添加任務(wù)(創(chuàng)建新的線程)
dispatch_async(globalQueue, ^{
NSLog(@"任務(wù) 1 --- %@", [NSThread currentThread]);
});
// 4. 使用同步函數(shù)向全局隊列中添加任務(wù)(在該線程中執(zhí)行)
dispatch_sync(globalQueue, ^{
NSLog(@"任務(wù) 2 --- %@", [NSThread currentThread]);
});
}
運行結(jié)果
總結(jié) : 該程序共創(chuàng)建了 3 個線程祈纯,包括 : 主線程、performSelectorInBackground: withObject: 創(chuàng)建的線程叼耙,使用異步函數(shù)創(chuàng)建的線程
4. 加載圖片
#import "LHLoadImageViewViewController.h"
@interface LHLoadImageViewViewController ()
@property (nonatomic, strong) UIImageView * imageView;
@property (nonatomic, strong) UIImage * image;
@end
@implementation LHLoadImageViewViewController
- (void)viewDidLoad {
[super viewDidLoad];
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, 300, 600)];
[self.view addSubview:_imageView];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 1. 獲取全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2. 使用異步函數(shù)將任務(wù)添加到全局隊列中腕窥,即由子線程加載圖片
dispatch_async(queue, ^{
NSLog(@"currentThread --- %@", [NSThread currentThread]);
// 1). 創(chuàng)建 URL
NSURL * url = [NSURL URLWithString:@"http://pic.58pic.com/58pic/16/58/28/80M58PICTcs_1024.jpg"];
// 2). 將 url 對應(yīng)的內(nèi)容轉(zhuǎn)換為 NSData 數(shù)據(jù)對象
NSData * data = [NSData dataWithContentsOfURL:url];
// 3). 用 NSData 數(shù)據(jù)對象的數(shù)據(jù)初始化 UIImage
_image = [UIImage imageWithData:data];
NSLog(@"加載圖片完成");
// 4). 回到主線程刷新 UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"currentThread --- %@", [NSThread currentThread]);
_imageView.image = _image;
});
});
}
@end
運行結(jié)果
總結(jié) : 通常主線程用來刷新 UI 界面,而子線程用來做一些耗時的工作(加載圖片等)筛婉,從上述運行結(jié)果可以看出簇爆,加載圖片由子線程執(zhí)行,而刷新 UI 則有主線程執(zhí)行
5. 延時方法
前面說過的 sleepForTimeInterval: 方法 和 sleepUntilDate: 方法都是針對當(dāng)前已經(jīng)執(zhí)行線程的爽撒,而本節(jié)所說的延時方法是針對還未執(zhí)行的線程
1)使用 performSelector: withObject: afterDelay: 方法
使用該方法可以將指定的任務(wù)延遲多少時間(單位為秒)執(zhí)行入蛆,并且該方法在哪個線程中被調(diào)用,那么指定的任務(wù)也就在哪個線程中執(zhí)行
- (void)test1 {
// 1. 獲取當(dāng)前線程(主線程)
NSThread * mainThread = [NSThread currentThread];
NSLog(@"currentThread -- %@", mainThread);
// 2. 延時 2s 調(diào)用(在本線程中)
[self performSelector:@selector(run:) withObject:@"延時2s" afterDelay:2.0];
}
- (void)run:(NSString *)arg {
// 1. 獲取當(dāng)前線程
NSThread * currentThread = [NSThread currentThread];
NSLog(@"currentThread --- %@", currentThread);
}
運行結(jié)果
總結(jié) : 可以看出匆浙,因為調(diào)用 performSelector: withObject: afterDelay: 方法所在的線程為主線程安寺,所以 run: 方法也在主線程中執(zhí)行,并且延時了 2s
2)將 performSelector: withObject: afterDelay: 放在同步/異步函數(shù)中
- (void)test2 {
NSLog(@"currentThread --- %@", [NSThread currentThread]);
// 1. 獲取主隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2. 使用異步函數(shù)向全局隊列添加任務(wù)
dispatch_async(queue, ^{
[self performSelector:@selector(run:) withObject:@"異步函數(shù)中執(zhí)行任務(wù)" afterDelay:4.0];
});
// 3. 使用同步函數(shù)向全局隊列添加任務(wù)
dispatch_sync(queue, ^{
[self performSelector:@selector(run:) withObject:@"同步函數(shù)中執(zhí)行任務(wù)" afterDelay:4.0];
});
}
- (void)run:(NSString *)arg {
// 1. 獲取當(dāng)前線程
NSThread * currentThread = [NSThread currentThread];
NSLog(@"currentThread --- %@ --- %@", currentThread, arg);
}
運行結(jié)果
總結(jié) : 可以看出首尼,只有同步函數(shù)執(zhí)行了任務(wù)挑庶,異步函數(shù)并沒有
可見言秸,將 performSelector: withObject: afterDelay: 方法 放在異步函數(shù)中是不起作用的
3)使用 dispatch_after 方法
// dispatch_after 函數(shù)
// 參數(shù) 1: 延時的時間
// 參數(shù) 2: 在哪個隊列中執(zhí)行
// 參數(shù) 3: 執(zhí)行任務(wù)的代碼塊
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
dispatch_time_t 是表示時間類型,可以通過下面的函數(shù)創(chuàng)建
// dispatch_time 函數(shù)
// 參數(shù) 1: 從何時開始迎捺,一般用 DISPATCH_TIME_NOW 表示從當(dāng)前開始
// 參數(shù) 2: 延時的秒數(shù)举畸,單位為 納秒
// 便于對參數(shù) 2 的方便使用,有定義以下宏
/* 注意凳枝,這三個宏的單位都是納秒
#define NSEC_PER_SEC 1000000000ull 每秒有多少納秒
#define USEC_PER_SEC 1000000ull 每秒有多少毫秒
#define NSEC_PER_USEC 1000ull 每毫秒有多少納秒
*/
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
延時 1s 將任務(wù)放到主隊列中的代碼如下
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
NSLog(@"延遲開始");
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"執(zhí)行任務(wù)中...");
NSLog(@"延遲結(jié)束");
});
運行結(jié)果