上一節(jié)我們分析了多線程的知識捞附,本節(jié)碳蛋,我們著重分析多線程中
使用最頻繁
的GCD
- GCD簡介
- 函數(shù)與隊列四大組合(同步、異步蚁袭、串行腮敌、并行)
- 性能調(diào)度耗能
- 面試題
- 線程資源共享
- 柵欄函數(shù)barrier
- 調(diào)度組 Group
- GCD單例
- 信號量 semaphore
1. GCD簡介
GCD
阱当,全稱Grand Central Dispatch
(中央調(diào)度中心)俏扩,純C語言
開發(fā),提供了很多強大的函數(shù)
弊添。
- GCD的優(yōu)勢:
- GCD是蘋果公司為
多核并行
運算提出的解決方案
录淡; - GCD會
自動利用
更多的CPU內(nèi)核
(比如雙核、四核等)油坝; - GCD會
自動管理
線程的生命周期
(創(chuàng)建線程嫉戚、調(diào)度任務(wù)、銷毀線程)澈圈。
程序員只需要告訴GCD
想要執(zhí)行的任務(wù)
彬檀,不需要
編寫任何線程管理
相關(guān)代碼(調(diào)度
、銷毀
都不用管
)
- GCD核心: 將
任務(wù)
添加到隊列
瞬女,并指定執(zhí)行任務(wù)的函數(shù)
這里引申出
任務(wù)
窍帝、隊列
、執(zhí)行任務(wù)的函數(shù)
三個內(nèi)容拆魏。我們一一進行分析
首先盯桦,我們展示一個簡單示例
:
- (void)syncTest {
// 任務(wù)(block)
dispatch_block_t block = ^{
NSLog(@"hello GCD");
};
// 隊列(此處串行隊列)
dispatch_queue_t queue = dispatch_queue_create("ht-syncTest", DISPATCH_QUEUE_SERIAL);
// 執(zhí)行任務(wù)的函數(shù)(此處異步函數(shù))
dispatch_async(queue, block);
}
- 借助示例,我們可以很好的理解
任務(wù)
渤刃、隊列
和執(zhí)行任務(wù)的函數(shù)
1.1 任務(wù)
GCD的任務(wù)
是使用block
封裝的函數(shù)
拥峦,沒有入?yún)?/code>和
返參
。
- 任務(wù)創(chuàng)建好后卖子,等待
執(zhí)行任務(wù)的函數(shù)
將其放入隊列
中略号。
拓展:
- 執(zhí)行block,需要調(diào)用
block()
洋闽,這步調(diào)用玄柠,是執(zhí)行任務(wù)的函數(shù)
內(nèi)部自動管理
。
后面解析dispatch源碼
時诫舅,可以清楚
知道調(diào)用時機
羽利。
1.2 隊列
GCD的隊列包含串行隊列
和并行隊列
兩種。
-
串行隊列:
同一時刻
只允許一個任務(wù)
執(zhí)行刊懈。(類似單車道
这弧,汽車只能一輛輛
排隊通過
) -
并行隊列:
同一時刻
允許多個任務(wù)
執(zhí)行。(類似多車道
虚汛,同時可以多輛
汽車通過
)
1.3 執(zhí)行任務(wù)的函數(shù)
執(zhí)行任務(wù)的函數(shù)
包括同步函數(shù)
和異步函數(shù)
兩種:
1.3.1 dispatch_sync
同步函數(shù):
- 必須
等待
當前語句執(zhí)行完畢
匾浪,才
會執(zhí)行下一條
語句 -
不
會開啟線程
,就在當前線程
執(zhí)行block任務(wù)
1.3.2 dispatch_async
異步函數(shù):
-
不用等待
當前語句執(zhí)行完畢
卷哩,就可以執(zhí)行下一條語句 -
會
開啟線程執(zhí)行block
任務(wù)
(在新線程
執(zhí)行還是空閑
的舊線程
執(zhí)行蛋辈,取決
于cpu的調(diào)度
)
異步
是多線程
的代名詞
多線程
的意義
,就是為了適當提高
執(zhí)行效率
将谊,開啟多個線程"同時"
執(zhí)行多個任務(wù)
冷溶。- 嚴格來說渐白,應(yīng)該是
并行異步
是多線程
的代名詞。因為只有并行
挂洛,才支持多通道
(車道),才能同時
執(zhí)行多個任務(wù)
礼预。
ps: (
下文中
提到的函數(shù)
,都指代執(zhí)行任務(wù)的函數(shù)
)
為了更好的理解這些概念虏劲,下面對函數(shù)與隊列
四大組合一一進行案例分析
2. 函數(shù)與隊列四大組合(同步托酸、異步、串行柒巫、并行)
- 主隊列
dispatch_get_main_queue
:
- 專門用來在
主線程
上調(diào)度任務(wù)
的串行隊列
-
不
會開啟線程
- 如果當前主線程
正在執(zhí)行
任務(wù)励堡,需要等
當前任務(wù)執(zhí)行完
,才會繼續(xù)調(diào)度其他
任務(wù)堡掏。
- 全局并發(fā)隊列
dispatch_get_global_queue
:
- 為了
方便
程序員的使用
应结,蘋果提供了全局隊列 (并發(fā)隊列
,實現(xiàn)多線程
需求的快捷方式
)泉唁。 - 使用
多線程開發(fā)
時鹅龄,如果對隊列沒有特殊要求
,可直接
使用全局隊列
來執(zhí)行異步任務(wù)
亭畜。)
拓展:
Q:隊列有幾種扮休?- (void)demo { // 串行隊列 dispatch_queue_t serial = dispatch_queue_create("ht", DISPATCH_QUEUE_SERIAL); // 并行隊列 dispatch_queue_t concurrent = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT); // 主隊列(串行隊列) dispatch_queue_t mainQueue = dispatch_get_main_queue(); // 全局隊列 (并行隊列) dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); NSLog(@"\n%@ \n%@ \n%@ \n%@", serial, concurrent, mainQueue, globalQueue); }
image.png
- A:只有
串行隊列
和并行隊列
兩種。
(底層:DQF_WIDTH
為1:表示串行隊列
拴鸵,DQF_WIDTH
大于1: 表示并行隊列
玷坠。詳細底層分析,下一節(jié)會講)
2.1 同步 + 串行 死鎖
- (void)mainSyncTest{
NSLog(@"0 %@", [NSThread currentThread]);
// 等
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"1 %@", [NSThread currentThread]);
});
NSLog(@"2 %@", [NSThread currentThread]);
}
- 打印結(jié)果(
打印0
之后劲藐,崩潰
):
- 分析:
-
主隊列
(main)是串行
隊列八堡,函數(shù)是sync
同步函數(shù)。屬于同步函數(shù)
&串行隊列
的情況 - 在打印
0
之后聘芜,dispatch_sync
同步函數(shù)將block
排隊插入mainSyncTest
函數(shù)最后兄渺,等待mainSyncTest
函數(shù)執(zhí)行完后再執(zhí)行。 - 但是
block
沒有執(zhí)行汰现,dispatch_sync
函數(shù)就等于沒有完成挂谍。程序無法往下執(zhí)行。 - 所以造成了
dispatch_sync
等mainSyncTest
執(zhí)行完后執(zhí)行block
服鹅,而mainSyncTest
卻說dispatch_sync
沒有執(zhí)行完凳兵,我無法結(jié)束百新。 ??
這里有一個誤區(qū)企软,
堵塞
與打印2
無關(guān)。
image.png真正的堵塞饭望,是由于
dispatch_sync
內(nèi)部的block
需要等mainSyncTest
全部執(zhí)行完再執(zhí)行仗哨,而mainSyncTest
函數(shù)需要等dispatch_sync
執(zhí)行完形庭。
- 借用一個笑話描述:
面試官
:你講清楚
了GCD的底層原理,我就錄用你
厌漂。
大牛
:你錄用我
萨醒,我就給你講
GCD的底層原理
為
加深理解
,分享今日騰訊前輩
的指點
:Q: 如果上述代碼苇倡,將
同步 + 主隊列
執(zhí)行改為同步 + 自定義串行隊列
富纸,是否會堵塞?
image.png
- 【疑問點】: 雖然將
主隊列執(zhí)行
改為自定義串行隊列
旨椒,解決了堵塞問題晓褪,但是否和syncTest
代碼本身運行的隊列
和線程
有關(guān)呢?[之前堵塞代碼]
函數(shù)代碼
和block代碼
都在主隊列+主線程
[新代碼]函數(shù)代碼
在自定義隊列+主線程
本著
求真
的原則综慎,我將syncTest
代碼也放在這個自定義隊列
中執(zhí)行涣仿,此時堵塞
又出現(xiàn)了:
image.png我們注意到
線程
還是在main
主線程。所以:
同一隊列同一線程進行sync
同步操作示惊,會阻塞,Crash繼續(xù)探索,按照上面說的纵装,
自定義隊列
切主隊列
相满,是否也可以阻止阻塞
呢?
答案是錯誤
image.png
主隊列
是個特殊隊列
阔拳,APP啟動
時就與主線程
完成了線程綁定
崭孤。不會切換線程
。
- 繼續(xù)探索糊肠,是不是由于
主隊列特殊
:
自定義隊列
切其他自定義隊列
辨宠,是否也可以阻止阻塞
呢?
答案是正確
image.png
- 是否和
主線程
有關(guān)货裹?如果當前函數(shù)
在子線程執(zhí)行
嗤形,任務(wù)
回歸主線程
操作,是否也可以阻止阻塞
呢弧圆?
答案是正確
image.png總結(jié):
主隊列
是個特殊隊列
赋兵,APP啟動
時就與主線程
完成了線程綁定
,不會切換線程
。
- 當前環(huán)境:
主隊列+主線程
搔预,執(zhí)行sync
同步 +main_queue
主隊列任務(wù)霹期,會阻塞- 當前環(huán)境:
主隊列+主線程
,切換到自定義串行隊列
拯田,不會開辟線程(block
在main
線程執(zhí)行),不會阻塞- 當前環(huán)境:
自定義串行隊列+主線程
历造,切換到主隊列
,會阻塞- 當前環(huán)境:
自定義串行隊列+主線程
,切換到新自定義串行隊列
吭产,不會開辟線程(block
在main
線程執(zhí)行),不會阻塞- 當前環(huán)境:
自定義串行隊列+子線程
侣监,切換到主隊列
,主隊列是綁定main
線程的臣淤,所以會切換回main線程
執(zhí)行block任務(wù)
橄霉,執(zhí)行完后回到子線程
,執(zhí)行后續(xù)任務(wù)邑蒋。不會阻塞
2.2 同步 + 并行
- (void)globalSyncTest{
for (int i = 0; i<20; i++) {
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
-
不
會阻塞
線程姓蜂,但是一次
只通過一個
。是耗時操作
医吊。
2.3 異步 + 串行
- (void)mainAsyncTest{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"1 %@", [NSThread currentThread]);
});
NSLog(@"2 %@", [NSThread currentThread]);
}
- 可以發(fā)現(xiàn)覆糟,
異步
+串行
時,異步函數(shù)內(nèi)的Block
(打印1)是在mainAsyncTest
函數(shù)全部執(zhí)行完后(打印了2)遮咖,再在新線程
中執(zhí)行block
滩字,打印了1。
可以對比上面
2.1 同步 + 串行
阻塞死鎖的現(xiàn)象御吞,兩者的區(qū)別是:同步 + 串行:
dispatch_sync
必須 等mainSyncTest
執(zhí)行完麦箍,才將block
任務(wù)插入尾部。
dispatch_sync
必須 等block
執(zhí)行完陶珠,才算完成挟裂。異步 + 串行:
1.
dispatch_async
不用等mainAsyncTest
執(zhí)行完,直接將block
任務(wù)插入尾部揍诽。
dispatch_async
不用等block
執(zhí)行完诀蓉,只要將block
插入尾部,就算完成了暑脆。
2.3 異步 + 并行
- (void)globalSyncTest{
for (int i = 0; i<20; i++) {
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
- 會
開啟多個線程
渠啤,執(zhí)行順序不確定
。
3. 性能調(diào)度耗能
測試代碼:
- (void)dissipation {
CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
dispatch_queue_t queue = dispatch_queue_create("ht-thread", DISPATCH_QUEUE_SERIAL);
// dispatch_async(queue, ^{
// NSLog(@"異步執(zhí)行");
// });
dispatch_sync(queue, ^{
NSLog(@"同步執(zhí)行");
});
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
}
- 對比
無任何操作
添吗、創(chuàng)建線程
沥曹、創(chuàng)建線程調(diào)用同步函數(shù)
、創(chuàng)建線程調(diào)用異步函數(shù)
四種情況的耗時:
-
無任何操作
時碟联,基本無耗時
image.png -
創(chuàng)建線程
: 耗時0.00009秒
image.png -
創(chuàng)建
線程且調(diào)用異步
函數(shù): 耗時0.00040秒
image.png -
創(chuàng)建線程
且調(diào)用同步函數(shù)
: 耗時0.000232秒
image.png
結(jié)論:
- 每次
創(chuàng)建線程
妓美,都會有時間
上的損耗
- 線程創(chuàng)建后,
同步執(zhí)行
比異步執(zhí)行
更耗時
4. 面試題
4.1 面試題一
- (void)demo{
// 串行隊列
dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
// 異步函數(shù)
dispatch_async(queue, ^{
NSLog(@"2");
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
});
NSLog(@"5");
}
- 打印
1
鲤孵、5
壶栋、2
后,崩潰
普监。
如果你掌握
了上面內(nèi)容
贵试,特別[圖片上傳中...(未命名.png-beed7-1604486756600-0)]
是我總結(jié)的同步 + 串行
與異步 + 串行
的區(qū)別熟悉了丧没。這題就難不住你了。
分析:
image.png
4.2 面試題二
- (void)textDemo{
// 并行隊列
dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1 %@",[NSThread currentThread]);
// 異步
dispatch_async(queue, ^{
NSLog(@"2 %@",[NSThread currentThread]);
// 同步
dispatch_sync(queue, ^{
NSLog(@"3 %@",[NSThread currentThread]);
});
NSLog(@"4 %@",[NSThread currentThread]);
});
NSLog(@"5 %@",[NSThread currentThread]);
}
打印結(jié)果:
1
->5
->2
->3
->4
與
面試題一
不同锡移,這里是DISPATCH_QUEUE_CONCURRENT
并行隊列。參考
2.2 同步+并行
分析漆际,并發(fā)隊列
中的dispatch_sync
同步函數(shù)不
會阻塞線程
淆珊,但是一次
只通過一個任務(wù)
。
4.3 面試題三
- (void)textDemo{
// 并行隊列
dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
// 耗時
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
- 打印結(jié)果:
1
->5
->2
->4
->3
4.4 面試題四
- 選出
打印順序
可能出現(xiàn)
的選項:
A: 1230789
B: 1237890
C: 3120798
D: 2137890
- (void)demo{
// 并行隊列
dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1");
});
dispatch_async(queue, ^{
NSLog(@"2");
});
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"0");
dispatch_async(queue, ^{
NSLog(@"7");
});
dispatch_async(queue, ^{
NSLog(@"8");
});
dispatch_async(queue, ^{
NSLog(@"9");
});
}
- 答案是
A
和C
分析:
- 是
并發(fā)隊列
奸汇;異步 & 并發(fā)
是無序
的施符,所以1
和2
的打印是無序
的,7
擂找、8
戳吝、9
的打印是無序
的;同步 & 并發(fā)
是排隊
一個個任務(wù)執(zhí)行
贯涎,所以0
一定在3
后面打印听哭,7、8塘雳、9
一定在0
后面打印陆盘。滿足
0
在3
后打印,7败明、8隘马、9
在0
后打印。只有選項A
和C
妻顶。
5. 線程資源共享
-
多讀單寫:
利用串行隊列
酸员,異步函數(shù)
支持多人買票
,同步函數(shù)
限制同一時刻
僅出一張票讳嘱。
@interface ViewController ()
@property (nonatomic, assign) NSInteger tickets; // 票數(shù)
@property (nonatomic, strong) dispatch_queue_t queue; // 隊列
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 準備票數(shù)
_tickets = 20;
// 創(chuàng)建串行隊列
_queue = dispatch_queue_create("ht", DISPATCH_QUEUE_SERIAL);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 第一個線程賣票
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self saleTickes];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 第二個線程賣票
[self saleTickes];
});
}
- (void)saleTickes {
while (self.tickets > 0) {
// 模擬延時
[NSThread sleepForTimeInterval:1.0];
// 蘋果不推薦程序員使用互斥鎖幔嗦,串行隊列同步任務(wù)可以達到同樣的效果!
// @synchronized
// 使用串行隊列沥潭,同步任務(wù)賣票
dispatch_sync(_queue, ^{
// 檢查票數(shù)
if (self.tickets > 0) {
self.tickets--;
NSLog(@"還剩 %zd %@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"沒有票了");
}
});
}
}
@end
6. 柵欄函數(shù)barrier
控制
任務(wù)執(zhí)行順序
崭添,同步
。
dispatch_barrier_async
: 前面任務(wù)都執(zhí)行完畢叛氨,才會到這里(不會堵塞線程)dispatch_barrier_sync
: 堵塞線程呼渣,等待前面任務(wù)都執(zhí)行完畢,才放開堵塞寞埠。堵塞期間屁置,后面的任務(wù)都被掛起等待。
重點:柵欄函數(shù)只能控制同一并發(fā)隊列
-
柵欄函數(shù)
只應(yīng)用在并行
隊列&異步
函數(shù)中仁连,它的作用就是在監(jiān)聽
多個信號(任務(wù))
是否都完成
蓝角。
(串行
或同步
內(nèi)的信號(任務(wù))
本身就是按順序執(zhí)行
阱穗,不需要使用到柵欄函數(shù)
。)
坑點:柵欄函數(shù)為何不能使用
dispatch_get_global_queue
隊列使鹅?因為
global
隊列中有很多系統(tǒng)任務(wù)
也在執(zhí)行
揪阶。 我們需要dispatch_queue_create
手動創(chuàng)建一個純凈
的隊列
,放置自己
需要執(zhí)行的任務(wù)
患朱,再使用柵欄函數(shù)
監(jiān)聽任務(wù)的執(zhí)行結(jié)果
鲁僚。
//MARK: -ViewController
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
__block CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
// 請求token
[self requestToken:^(id value) {
// 帶token
[weakSelf requestDataWithToken:value handle:^(BOOL success) {
success ? NSLog(@"成功") : NSLog(@"失敗");
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
}];
}];
}
/** 獲取token請求 */
- (void)requestToken:(void(^)(id value))successBlock{
NSLog(@"開始請求token");
[NSThread sleepForTimeInterval:1];
if (successBlock) {
successBlock(@"b2a8f8523ab41f8b4b9b2a79ff47c3f1");
}
}
/** 請求所有數(shù)據(jù) */
- (void)requestDataWithToken: (NSString *)token handle: (void(^)(BOOL success))successBlock {
dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[self requestHeadDataWithToken: token handle:^(id value) { NSLog(@"%@", value); }];
});
dispatch_async(queue, ^{
[self requestListDataWithToken:token handle:^(id value) { NSLog(@"%@", value); }];
});
dispatch_barrier_async(queue, ^{ successBlock(true); });
}
/** 頭部數(shù)據(jù)的請求 */
- (void)requestHeadDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
if (token.length == 0) {
NSLog(@"沒有token,因為安全性無法請求數(shù)據(jù)");
return;
}
[NSThread sleepForTimeInterval:2];
if (successBlock) {
successBlock(@"我是頭,都聽我的");
}
}
/** 列表數(shù)據(jù)的請求 */
- (void)requestListDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
if (token.length == 0) {
NSLog(@"沒有token,因為安全性無法請求數(shù)據(jù)");
return;
}
[NSThread sleepForTimeInterval:1];
if (successBlock) {
successBlock(@"我是列表數(shù)據(jù)");
}
}
@end
7. 調(diào)度組 Group
與柵欄函數(shù)
類似,也是控制
任務(wù)的執(zhí)行順序
裁厅。
-
dispatch_group_create
創(chuàng)建組 -
dispatch_group_async
進組任務(wù) (自動管理進組
和出組
) -
dispatch_group_notify
進組任務(wù)執(zhí)行完畢通知 -
dispatch_group_wait
進組任務(wù)執(zhí)行等待時間
dispatch_group_enter
進組dispatch_group_leave
出組
進組
和出組
需要成對搭配
使用代碼案例
//MARK: -ViewController
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
__block CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
// 1. 【手動入組和出組】
[self requestToken:^(id value) {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t concurrent = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_enter(group);
dispatch_async(concurrent, ^{
[weakSelf requestHeadDataWithToken:value handle:^(id value) {
NSLog(@"%@",value);
dispatch_group_leave(group);
}];
});
dispatch_group_enter(group);
dispatch_async(concurrent, ^{
[weakSelf requestListDataWithToken:value handle:^(id value) {
NSLog(@"%@",value);
dispatch_group_leave(group);
}];
});
dispatch_group_notify(group, concurrent, ^{
NSLog(@"成功了");
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
});
}];
// // 2. 【自動入組和出組】
// [self requestToken:^(id value) {
// dispatch_group_t group = dispatch_group_create();
// dispatch_queue_t concurrent = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
//
// dispatch_group_async(group, concurrent, ^{
// [weakSelf requestHeadDataWithToken:value handle:^(id value) {
// NSLog(@"%@",value);
// }];
// });
//
// dispatch_group_async(group, concurrent, ^{
// [weakSelf requestListDataWithToken:value handle:^(id value) {
// NSLog(@"%@",value);
// }];
// });
//
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// NSLog(@"成功了");
// NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
// });
//
// }];
// // 3. 【同步函數(shù) + 自動入組出組】
// __block NSString * token;
// dispatch_sync(dispatch_queue_create("ht", DISPATCH_QUEUE_SERIAL), ^{
// [self requestToken:^(id value) {
// token = value;
// }];
// });
//
// dispatch_group_t group = dispatch_group_create();
// dispatch_queue_t concurrent = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
//
// dispatch_group_async(group, concurrent, ^{
// [weakSelf requestHeadDataWithToken: token handle:^(id value) {
// NSLog(@"%@",value);
// }];
// });
//
// dispatch_group_async(group, concurrent, ^{
// [weakSelf requestListDataWithToken: token handle:^(id value) {
// NSLog(@"%@",value);
// }];
// });
//
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// NSLog(@"成功了");
// NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
// });
}
/** 獲取token請求 */
- (void)requestToken:(void(^)(id value))successBlock{
NSLog(@"開始請求token");
[NSThread sleepForTimeInterval:1];
if (successBlock) {
successBlock(@"b2a8f8523ab41f8b4b9b2a79ff47c3f1");
}
}
/** 頭部數(shù)據(jù)的請求 */
- (void)requestHeadDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
if (token.length == 0) {
NSLog(@"沒有token,因為安全性無法請求數(shù)據(jù)");
return;
}
[NSThread sleepForTimeInterval:2];
if (successBlock) {
successBlock(@"我是頭,都聽我的");
}
}
/** 列表數(shù)據(jù)的請求 */
- (void)requestListDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
if (token.length == 0) {
NSLog(@"沒有token,因為安全性無法請求數(shù)據(jù)");
return;
}
[NSThread sleepForTimeInterval:1];
if (successBlock) {
successBlock(@"我是列表數(shù)據(jù)");
}
}
@end
8. GCD單例
- 單例:
- 利用
static
在內(nèi)存中僅一份
的特性冰沙,保證了對象的唯一性
。 - 重寫
allocWithZone
的實現(xiàn)执虹,讓外界使用alloc
創(chuàng)建時拓挥,永遠返回的是static
聲明的對象
。
- 以下是
KCImageManger
的核心代碼:
#import "KCImageManger.h"
// 保存在常量區(qū)
static id instance;
@implementation KCImageManger
/**
每次類初始化的時候進行調(diào)用
1袋励、+load它不遵循那套繼承規(guī)則侥啤。如果某個類本身沒有實現(xiàn)+load方法,那么不管其它各級超類是否實現(xiàn)此方法茬故,系統(tǒng)都不會調(diào)用愿棋。+load方法調(diào)用順序是:SuperClass -->SubClass --> CategaryClass。
3均牢、+initialize是在類或者它的子類接受第一條消息前被調(diào)用糠雨,但是在它的超類接收到initialize之后。也就是說+initialize是以懶加載的方式被調(diào)用的徘跪,如果程序一直沒有給某個類或它的子類發(fā)送消息甘邀,那么這個類的+initialize方法是不會被調(diào)用的。
4垮庐、只有執(zhí)行+initialize的那個線程可以操作類或類實例松邪,其他線程都要阻塞等著+initialize執(zhí)行完。
5哨查、+initialize 本身類的調(diào)用都會執(zhí)行父類和分類實現(xiàn) initialize方法都會被調(diào)多次
*/
+ (void)initialize{
NSLog(@"父類");
if (instance == nil) {
instance = [[self alloc] init];
}
}
/**
配合上面 也能進行單利
*/
+ (instancetype)manager{
return instance;
}
/**
* 所有為類的對象分配空間的方法逗抑,最終都會調(diào)用到 allovWithZone 方法
* 下面這樣的操作相當于鎖死 該類的所有初始化方法
*/
+(instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [super allocWithZone:zone];
});
return instance;
}
/**
單利
*/
+(instancetype)shareManager{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
@end
- 測試代碼:
- (void)onceDemo{
// KCImageManger *manger1 = [KCImageManger shareManager];
// KCImageManger *manger2 = [KCImageManger shareManager];
// KCImageManger *manger3 = [KCImageManger shareManager];
// KCImageManger *manger1 = [[KCImageManger alloc] init];
// KCImageManger *manger2 = [[KCImageManger alloc] init];
// KCImageManger *manger3 = [KCImageManger new];
KCImageManger *manger1 = [[KCImageManger alloc] init];
KCImageManger *manger2 = [KCImageManger manager];
KCImageManger *manger3 = [KCImageManger manager];
NSLog(@"%@---%@---%@",manger1,manger2,manger3);
}
- 打印結(jié)果:
9. 信號量 semaphore
控制GCD
的最大并發(fā)數(shù)
。(同一時刻
可進行的信號(任務(wù))
最大個數(shù)寒亥。)
-
dispatch_semaphore_create
: 創(chuàng)建信號量 -
dispatch_semaphore_wait
: 信號量等待 -
dispatch_semaphore_signal
: 信號量釋放
加入了信號量的等待dispatch_semaphore_wait
后邮府,一定需要配對
加入信號量釋放dispatch_semaphore_signal
,不然會crash
- 示例代碼:
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建全局隊列(并行)
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 設(shè)置信號量
dispatch_semaphore_t sem = dispatch_semaphore_create(2); // 最多同時執(zhí)行2個任務(wù)
//任務(wù)1
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
sleep(1);
NSLog(@"執(zhí)行任務(wù)1");
sleep(1);
NSLog(@"任務(wù)1完成");
dispatch_semaphore_signal(sem);
});
//任務(wù)2
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
sleep(1);
NSLog(@"執(zhí)行任務(wù)2");
sleep(1);
NSLog(@"任務(wù)2完成");
dispatch_semaphore_signal(sem);
});
//任務(wù)3
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
sleep(1);
NSLog(@"執(zhí)行任務(wù)3");
sleep(1);
NSLog(@"任務(wù)3完成");
dispatch_semaphore_signal(sem);
});
}
下一節(jié)溉奕,分析 dispatch源碼