GCD,全稱是 Grand Central Dispatch,純 C 語言陡舅,提供了非常多強(qiáng)大的函數(shù). 是蘋果公司為多核的并行運(yùn)算提出的解決方案, 會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核), 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程), 程序員只需要告訴 GCD 想要執(zhí)行什么任務(wù)释漆,不需要編寫任何線程管理代碼.
核心概念
1. 將任務(wù)添加到隊(duì)列代态,并且指定執(zhí)行任務(wù)的函數(shù)
2. 任務(wù)使用block封裝
? ? ? ? 任務(wù)的block沒有參數(shù)也沒有返回值
3. 執(zhí)行任務(wù)的函數(shù)
? ? ? ?異步dispatch_async
? ? ? ? ? ?不用等待當(dāng)前語句執(zhí)行完畢,就可以執(zhí)行下一條語句
? ? ? ? ? ?會(huì)開啟線程執(zhí)行block的任務(wù)
? ? ? ? ? ?異步是多線程的代名詞
? ? ? ?同步dispatch_sync
? ? ? ? ? ? ?必須等待當(dāng)前語句執(zhí)行完畢菇晃,才會(huì)執(zhí)行下一條語句
? ? ? ? ? ? ?不會(huì)開啟線程
? ? ? ? ? ? 在當(dāng)前執(zhí)行block的任務(wù)
4. 隊(duì)列- 系統(tǒng)以先進(jìn)先出的方式調(diào)度隊(duì)列中的任務(wù)執(zhí)行
? ? ? ? 串行隊(duì)列
? ? ? ? ? ? ? 一次只能"調(diào)度"一個(gè)任務(wù)?
? ? ? ? ? ? ?dispatch_queue_create("cn.itcast.queue", NULL);
? ? ? ?并發(fā)隊(duì)列
? ? ? ? ? ? ? 一次可以"調(diào)度"多個(gè)任務(wù)
? ? ? ? ? ? ?dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);
? ? ? ?主隊(duì)列
? ? ? ? ? ? 專門用來在主線程上調(diào)度任務(wù)的隊(duì)列
? ? ? ? ? ? 不會(huì)開啟線程
? ? ? ? ? ? ? 在主線程空閑時(shí)才會(huì)調(diào)度隊(duì)列中的任務(wù)在主線程執(zhí)行
? ? ? ? ? ? ? dispatch_get_main_queue();
? ? ? 全局隊(duì)列
? ? ? ? ? ? 為了方便程序員的使用,蘋果提供了全局隊(duì)列
dispatch_get_global_queue(0, 0)
? ? ? ? ? ? 全局隊(duì)列是一個(gè)并發(fā)隊(duì)列
? ? ? ? ? ? 在使用多線程開發(fā)時(shí)蚓挤,如果對(duì)隊(duì)列沒有特殊需求磺送,在執(zhí)行異步任務(wù)時(shí),可以直接使 ? ? ? ? ? 用全局隊(duì)列
小結(jié):
開不開線程由執(zhí)行任務(wù)的函數(shù)決定
? ? ? ? ?異步開灿意,異步是多線程的代名詞
? ? ? ? ?同步不開
開幾條線程由隊(duì)列決定
? ? ? ? 串行隊(duì)列開一條線程
? ? ? ? 并發(fā)隊(duì)列開多條線程册着,具體能開的線程數(shù)量由底層線程池決定
? ? ? ? ? ? ? ? iOS 8.0 之后,GCD 能夠開啟非常多的線程
? ? ? ? ? ? ? ? iOS 7.0 以及之前脾歧,GCD 通常只會(huì)開啟 5~6 條線程
- 隊(duì)列的選擇
多線程的目的:將耗時(shí)的操作放在后臺(tái)執(zhí)行甲捏!
串行隊(duì)列,只開一條線程鞭执,所有任務(wù)順序執(zhí)行
? ? ? ? ?如果任務(wù)有先后執(zhí)行順序的要求
? ? ? ? ?效率低 -> 執(zhí)行慢 -> "省電"
? ? ? ? 有的時(shí)候司顿,用戶其實(shí)不希望太快!例如使用 3G 流量兄纺,"省錢"
并發(fā)隊(duì)列大溜,會(huì)開啟多條線程,所有任務(wù)不按照順序執(zhí)行
? ? ? ?如果任務(wù)沒有先后執(zhí)行順序的要求
? ? ? ?效率高 -> 執(zhí)行快 -> "費(fèi)電"
? ? ? ?WIFI估脆,包月
實(shí)際開發(fā)中钦奋,線程數(shù)量如何決定?
? ? ? ?WIFI 線程數(shù)6條
? ? ? ?3G / 4G 移動(dòng)開發(fā)的時(shí)候,2~3條,再多會(huì)費(fèi)電費(fèi)錢付材!
全局隊(duì)列 和 主隊(duì)列
全局隊(duì)列
為了方便程序員的使用朦拖,蘋果提供了全局隊(duì)列dispatch_get_global_queue(0, 0)
全局隊(duì)列是一個(gè)并發(fā)隊(duì)列,馬上會(huì)講
在使用多線程開發(fā)時(shí)厌衔,如果對(duì)隊(duì)列沒有特殊需求璧帝,在執(zhí)行異步任務(wù)時(shí),可以直接使用全局隊(duì)列
主隊(duì)列
為了方便線程間通訊富寿,異步執(zhí)行完網(wǎng)絡(luò)任務(wù)睬隶,在主線程更新 UI
蘋果提供了主隊(duì)列dispatch_get_main_queue()
主隊(duì)列專門用于在主線程上調(diào)度任務(wù)執(zhí)行
異步執(zhí)行任務(wù)
- (void)gcdDemo1 {
// 1. 全局隊(duì)列
dispatch_queue_tqueue = dispatch_get_global_queue(0,0);
// 2. 任務(wù)
dispatch_block_t task = ^ {
NSLog(@"hello gcd %@", [NSThreadcurrentThread]);
? ? };
// 3. 將任務(wù)添加到隊(duì)列,并且指定異步執(zhí)行
dispatch_async(queue, task);
}
注意:如果等待時(shí)間長(zhǎng)一些页徐,會(huì)發(fā)現(xiàn)線程的number發(fā)生變化苏潜,由此可以推斷gcd 底層線程池的工作
精簡(jiǎn)代碼
模擬循環(huán)添加 10 個(gè)異步下載圖像的人物
// 精簡(jiǎn)代碼
- (void)gcdDemo2 {
for(NSInteger i =0; i <10; i++) {
? ? ? ? [NSThread sleepForTimeInterval:0.5];
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSLog(@"下載圖像 %zd %@", i, [NSThreadcurrentThread]);
? ? ? ? });
? ? }
}
NSThread 實(shí)現(xiàn)等價(jià)代碼對(duì)比
#pragma mark - NSThread 代碼
- (void)threadDemo {
for(NSInteger i =0; i <10; i++) {
? ? ? ? [NSThread sleepForTimeInterval:0.5];
? ? ? ? [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:@(i)];
? ? }
}
- (void)downloadImage:(id)obj {
NSLog(@"下載圖像 %@ %@", obj, [NSThreadcurrentThread]);
}
與NSThread的對(duì)比
1.所有的代碼寫在一起的,讓代碼更加簡(jiǎn)單变勇,易于閱讀和維護(hù)
? ? ? ? ?NSThread通過@selector指定要執(zhí)行的方法恤左,代碼分散
? ? ? ? ?GCD通過block指定要執(zhí)行的代碼,代碼集中
2.使用GCD不需要管理線程的創(chuàng)建/銷毀/復(fù)用的過程贰锁!程序員不用關(guān)心線程的生命周期
3.如果要開多個(gè)線程N(yùn)SThread必須實(shí)例化多個(gè)線程對(duì)象
4.NSThread靠NSObject的分類方法實(shí)現(xiàn)的線程間通訊赃梧,GCD靠block
線程間通訊
#pragma mark - 線程間通訊
- (void)gcdDemo3 {
dispatch_async(dispatch_get_global_queue(0,0), ^{
// 異步下載圖像
NSLog(@"下載圖像 %@", [NSThread currentThread]);
// 主線程更新
UIdispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"主線程更新 UI %@", [NSThread currentThread]);
? ? ? ? });
? ? });
}
以上代碼是GCD最常用代碼組合滤蝠!
網(wǎng)絡(luò)下載圖片
- (void)viewDidLoad {
? ? [super viewDidLoad];
// 1. 準(zhǔn)備
URLNSString*urlString =@"http://image.tianjimedia.com/uploadImages/2011/286/8X76S7XD89VU.jpg";
NSURL*url = [NSURLURLWithString:urlString];
// 2. 下載圖像
dispatch_async(dispatch_get_global_queue(0,0), ^{
// 1> 所有從網(wǎng)絡(luò)返回的都是二進(jìn)制數(shù)據(jù)
NSData*data = [NSData dataWithContentsOfURL:url];
// 2> 將二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成 image
UIImage*image = [UIImage imageWithData:data];
// 3> 主線程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 1> 設(shè)置圖像
_imageView.image= image;
// 2> 調(diào)整大小
[_imageView sizeToFit];
// 3> 設(shè)置 contentSize
_scrollView.contentSize= image.size;
? ? ? ? });
? ? });
}
串行隊(duì)列
特點(diǎn)
以先進(jìn)先出的方式豌熄,順序調(diào)度隊(duì)列中的任務(wù)執(zhí)行
無論隊(duì)列中所指定的執(zhí)行任務(wù)函數(shù)是同步還是異步,都會(huì)等待前一個(gè)任務(wù)執(zhí)行完成后物咳,再調(diào)度后面的任務(wù)
隊(duì)列創(chuàng)建
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue",NULL);
串行隊(duì)列 同步執(zhí)行
/// 串行隊(duì)列 - 同步執(zhí)行
/// 提問:是否開線程锣险?是否順序執(zhí)行?come here 的位置览闰?
/// 回答:不會(huì)開線程/順序執(zhí)行/最后
- (void)gcdDemo1 {
// 1. 串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_SERIAL);
// 2. 添加同步執(zhí)行的任務(wù)
for(NSInteger i =0; i <10; i++) {
dispatch_sync(queue, ^{
? ? ? ? ? ? [NSThread sleepForTimeInterval:0.5];
NSLog(@"%@ %zd", [NSThread currentThread], i);
? ? ? ? });
? ? }
NSLog(@"come here");
}
串行隊(duì)列 異步執(zhí)行
/// 串行隊(duì)列 - 異步執(zhí)行
/// 提問:是否開線程芯肤?是否順序執(zhí)行?come here 的位置压鉴?
/// 回答:會(huì)開一條線程/順序執(zhí)行/不確定
- (void)gcdDemo2 {
// 1. 串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_SERIAL);
// 2. 添加異步執(zhí)行的任務(wù)
for(NSInteger i =0; i <10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@ %zd", [NSThread currentThread], i);
? ? ? ? });
? ? }
NSLog(@"come here");
}
并發(fā)隊(duì)列
特點(diǎn)
以先進(jìn)先出的方式崖咨,并發(fā)調(diào)度隊(duì)列中的任務(wù)執(zhí)行
如果當(dāng)前調(diào)度的任務(wù)是同步執(zhí)行的,會(huì)等待任務(wù)執(zhí)行完成后油吭,再調(diào)度后續(xù)的任務(wù)
如果當(dāng)前調(diào)度的任務(wù)是異步執(zhí)行的击蹲,同時(shí)底層線程池有可用的線程資源,會(huì)再新的線程調(diào)度后續(xù)任務(wù)的執(zhí)行
隊(duì)列創(chuàng)建
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);
并發(fā)隊(duì)列 異步執(zhí)行
/// 并發(fā)隊(duì)列 - 異步執(zhí)行
/// 提問:是否開線程婉宰?是否順序執(zhí)行歌豺?come here 的位置?
/// 回答:會(huì)開線程(取決于底層線程池可用資源)/不是順序執(zhí)行/不確定
- (void)gcdDemo2 {
// 1. 并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);
// 2. 添加異步執(zhí)行的任務(wù)
for(NSInteger i =0; i <10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@ %zd", [NSThread currentThread], i);
? ? ? ? });
? ? }
NSLog(@"come here");
}
并發(fā)隊(duì)列 同步執(zhí)行
/// 并發(fā)隊(duì)列 - 同步執(zhí)行
/// 提問:是否開線程心包?是否順序執(zhí)行类咧?come here 的位置?
/// 回答:不開線程/順序執(zhí)行/最后
- (void)gcdDemo1 {
// 1. 并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);
// 2. 添加同步執(zhí)行的任務(wù)
for(NSInteger i =0; i <10; i++) {
dispatch_sync(queue, ^{
? ? ? ? ? ? [NSThread sleepForTimeInterval:0.5];
NSLog(@"%@ %zd", [NSThread currentThread], i);?
? ? ? });
? ? }
NSLog(@"come here");
}
主隊(duì)列
特點(diǎn)
專門用來在主線程上調(diào)度任務(wù)的隊(duì)列
不會(huì)開啟線程
以先進(jìn)先出的方式,在主線程空閑時(shí)才會(huì)調(diào)度隊(duì)列中的任務(wù)在主線程執(zhí)行
如果當(dāng)前主線程正在有任務(wù)執(zhí)行痕惋,那么無論主隊(duì)列中當(dāng)前被添加了什么任務(wù)区宇,都不會(huì)被調(diào)度
隊(duì)列獲取
主隊(duì)列是負(fù)責(zé)在主線程調(diào)度任務(wù)的
會(huì)隨著程序啟動(dòng)一起創(chuàng)建
主隊(duì)列只需要獲取不用創(chuàng)建
dispatch_queue_t queue = dispatch_get_main_queue();
主隊(duì)列,異步執(zhí)行
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {
? ? [self gcdDemo1];
? ? [NSThread sleepForTimeInterval:1.0];
NSLog(@"over");}
/// 主隊(duì)列異步
/// 提問:是否開線程血巍?是否順序執(zhí)行萧锉?come here 的位置?
/// 回答:不開/順序/最前面
- (void)gcdDemo1 {
// 1. 主隊(duì)列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2. 異步任務(wù)
for(NSInteger i =0; i <10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@", [NSThread currentThread]);
? ? ? ? });
? ? }
NSLog(@"come here");
}
在主線程空閑時(shí)才會(huì)調(diào)度主隊(duì)列中的任務(wù)在主線程執(zhí)行
主隊(duì)列述寡,同步執(zhí)行
/// 主隊(duì)列同步
- (void)gcdDemo2 {
NSLog(@"begin");
// 1. 主隊(duì)列
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"hello");?
? });
NSLog(@"end");
}
主隊(duì)列和主線程相互等待會(huì)造成死鎖
全局隊(duì)列
是系統(tǒng)為了方便程序員開發(fā)提供的柿隙,其工作表現(xiàn)與并發(fā)隊(duì)列一致
全局隊(duì)列 & 并發(fā)隊(duì)列的區(qū)別
全局隊(duì)列
沒有名稱
無論 MRC & ARC 都不需要考慮釋放
日常開發(fā)中,建議使用"全局隊(duì)列"
并發(fā)隊(duì)列
有名字鲫凶,和NSThread的name屬性作用類似
如果在 MRC 開發(fā)時(shí)禀崖,需要使用dispatch_release(q);釋放相應(yīng)的對(duì)象
dispatch_barrier必須使用自定義的并發(fā)隊(duì)列
開發(fā)第三方框架時(shí),建議使用并發(fā)隊(duì)列
全局隊(duì)列 異步任務(wù)
/**
提問:是否開線程螟炫?是否順序執(zhí)行波附?come here 的位置?
*/
- (void)gcdDemo8 {
// 1. 隊(duì)列
dispatch_queue_t q = dispatch_get_global_queue(0,0);
// 2. 執(zhí)行任務(wù)
for(int i =0; i <10; ++i) {
dispatch_async(q, ^{
NSLog(@"%@ - %d", [NSThread currentThread], i);
? ? ? ? });
? ? }
NSLog(@"come here");
}
運(yùn)行效果與并發(fā)隊(duì)列相同
參數(shù)
服務(wù)質(zhì)量(隊(duì)列對(duì)任務(wù)調(diào)度的優(yōu)先級(jí))/iOS 7.0 之前昼钻,是優(yōu)先級(jí)
iOS 8.0(新增掸屡,暫時(shí)不能用,今年年底)
QOS_CLASS_USER_INTERACTIVE0x21, 用戶交互(希望最快完成-不能用太耗時(shí)的操作)
QOS_CLASS_USER_INITIATED0x19, 用戶期望(希望快然评,也不能太耗時(shí))
QOS_CLASS_DEFAULT0x15, 默認(rèn)(用來底層重置隊(duì)列使用的仅财,不是給程序員用的)
QOS_CLASS_UTILITY0x11, 實(shí)用工具(專門用來處理耗時(shí)操作!)
QOS_CLASS_BACKGROUND0x09, 后臺(tái)
QOS_CLASS_UNSPECIFIED0x00, 未指定碗淌,可以和iOS 7.0 適配
iOS 7.0
DISPATCH_QUEUE_PRIORITY_HIGH2 高優(yōu)先級(jí)
DISPATCH_QUEUE_PRIORITY_DEFAULT0 默認(rèn)優(yōu)先級(jí)
DISPATCH_QUEUE_PRIORITY_LOW(-2) 低優(yōu)先級(jí)
DISPATCH_QUEUE_PRIORITY_BACKGROUNDINT16_MIN 后臺(tái)優(yōu)先級(jí)
為未來保留使用的盏求,應(yīng)該永遠(yuǎn)傳入0
結(jié)論:如果要適配 iOS 7.0 & 8.0,使用以下代碼:dispatch_get_global_queue(0, 0);
單例
有的時(shí)候亿眠,在程序開發(fā)中碎罚,有些代碼只想從程序啟動(dòng)就只執(zhí)行一次,典型的應(yīng)用場(chǎng)景就是“單例”
單例的特點(diǎn)
在內(nèi)存中只有一個(gè)實(shí)例
提供一個(gè)全局的訪問點(diǎn)
提示:?jiǎn)卫氖褂迷?iOS 中非常普遍纳像,以下代碼在很多公司的面試中荆烈,都要求能夠手寫出來
- (void)once {
static dispatch_once_t onceToken;
NSLog(@"%zd", onceToken);
// onceToken == 0 的時(shí)候執(zhí)行 block 中的代碼
// block 中的代碼是同步執(zhí)行的
dispatch_once(&onceToken, ^{
NSLog(@"執(zhí)行了");
? ? });
NSLog(@"come here");
}
dispatch 內(nèi)部也有一把鎖,是能夠保證"線程安全"的竟趾!而且是蘋果公司推薦使用的
以下代碼用于測(cè)試多線程的一次性執(zhí)行
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {
for(NSInteger i =0; i <10; i++) {
dispatch_async(dispatch_get_global_queue(0,0), ^{
? ? ? ? ? ? [self once];
? ? ? ? });?
? }
}
單例的特點(diǎn)
在內(nèi)存中只有一個(gè)實(shí)例
提供一個(gè)全局的訪問點(diǎn)
懶漢式單例實(shí)現(xiàn)
所謂懶漢式單例憔购,表示在使用時(shí)才會(huì)創(chuàng)建
@implementation NetworkTools
+ (instancetype)sharedTools {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
? ? ? ? instance = [[self alloc] init];
? ? });
return instance;
}
@end
面試時(shí)只要實(shí)現(xiàn)上面sharedTools方法即可
餓漢式單例實(shí)現(xiàn)
所謂餓漢式單例,表示盡早地創(chuàng)建單例實(shí)例潭兽,可以利用initialize方法建立單例
static id instance;
/// initialize 會(huì)在類第一次被使用時(shí)調(diào)用
/// initialize 方法的調(diào)用是線程安全的
+ (void)initialize {
NSLog(@"創(chuàng)建單例");
? ? instance = [[self alloc] init];
}
+ (instancetype)sharedTools {
return instance;
}