在這兩部分的系列中采驻,第一個(gè)部分的將解釋 GCD 是做什么的审胚,并從許多基本的 GCD 函數(shù)中找出幾個(gè)來展示。在第二部分挑宠,你將學(xué)到幾個(gè) GCD 提供的高級函數(shù)菲盾。
什么是 GCD
GCD 是 libdispatch 的市場名稱,而 libdispatch 作為 Apple 的一個(gè)庫各淀,為并發(fā)代碼在多核硬件(跑 iOS 或 OS X )上執(zhí)行提供有力支持懒鉴。它具有以下優(yōu)點(diǎn):
1.GCD 能通過推遲昂貴計(jì)算任務(wù)并在后臺運(yùn)行它們來改善你的應(yīng)用的響應(yīng)性能。
2.GCD 提供一個(gè)易于使用的并發(fā)模型而不僅僅只是鎖和線程碎浇,以幫助我們避開并發(fā)陷阱临谱。
3.GCD 具有在常見模式(例如單例)上用更高性能的原語優(yōu)化你的代碼的潛在能力。
本教程假設(shè)你對 Block 和 GCD 有基礎(chǔ)了解奴璃。如果你對 GCD 完全陌生悉默,先看看 iOS 上的多線程和 GCD 入門教程 學(xué)習(xí)其要領(lǐng)。
GCD 術(shù)語
要理解 GCD 苟穆,你要先熟悉與線程和并發(fā)相關(guān)的幾個(gè)概念抄课。這兩者都可能模糊和微妙,所以在開始 GCD 之前先簡要地回顧一下它們雳旅。
Serial vs. Concurrent 串行 vs. 并發(fā)
這些術(shù)語描述當(dāng)任務(wù)相對于其它任務(wù)被執(zhí)行跟磨,任務(wù)串行執(zhí)行就是每次只有一個(gè)任務(wù)被執(zhí)行,任務(wù)并發(fā)執(zhí)行就是在同一時(shí)間可以有多個(gè)任務(wù)被執(zhí)行攒盈。
雖然這些術(shù)語被廣泛使用抵拘,本教程中你可以將任務(wù)設(shè)定為一個(gè) Objective-C 的 Block 。不明白什么是 Block 型豁?看看 iOS 5 教程中的如何使用 Block 僵蛛。實(shí)際上,你也可以在 GCD 上使用函數(shù)指針迎变,但在大多數(shù)場景中充尉,這實(shí)際上更難于使用。Block 就是更加容易些氏豌!
Synchronous vs. Asynchronous 同步 vs. 異步
在 GCD 中喉酌,這些術(shù)語描述當(dāng)一個(gè)函數(shù)相對于另一個(gè)任務(wù)完成,此任務(wù)是該函數(shù)要求 GCD 執(zhí)行的泵喘。一個(gè)同步函數(shù)只在完成了它預(yù)定的任務(wù)后才返回。
一個(gè)異步函數(shù)般妙,剛好相反纪铺,會立即返回,預(yù)定的任務(wù)會完成但不會等它完成碟渺。因此鲜锚,一個(gè)異步函數(shù)不會阻塞當(dāng)前線程去執(zhí)行下一個(gè)函數(shù)。
注意——當(dāng)你讀到同步函數(shù)“阻塞(Block)”當(dāng)前線程,或函數(shù)是一個(gè)“阻塞”函數(shù)或阻塞操作時(shí)芜繁,不要被搞糊涂了旺隙!動(dòng)詞“阻塞”描述了函數(shù)如何影響它所在的線程而與名詞“代碼塊(Block)”沒有關(guān)系。代碼塊描述了用 Objective-C 編寫的一個(gè)匿名函數(shù)骏令,它能定義一個(gè)任務(wù)并被提交到 GCD 蔬捷。
譯者注:中文不會有這個(gè)問題,“阻塞”和“代碼塊”是兩個(gè)詞榔袋。
Critical Section 臨界區(qū)
就是一段代碼不能被并發(fā)執(zhí)行周拐,也就是,兩個(gè)線程不能同時(shí)執(zhí)行這段代碼凰兑。這很常見妥粟,因?yàn)榇a去操作一個(gè)共享資源,例如一個(gè)變量若能被并發(fā)進(jìn)程訪問吏够,那么它很可能會變質(zhì)(譯者注:它的值不再可信)勾给。
Race Condition 競態(tài)條件
這種狀況是指基于特定序列或時(shí)機(jī)的事件的軟件系統(tǒng)以不受控制的方式運(yùn)行的行為,例如程序的并發(fā)任務(wù)執(zhí)行的確切順序锅知。競態(tài)條件可導(dǎo)致無法預(yù)測的行為播急,而不能通過代碼檢查立即發(fā)現(xiàn)。
Deadlock 死鎖
兩個(gè)(有時(shí)更多)東西——在大多數(shù)情況下喉镰,是線程——所謂的死鎖是指它們都卡住了旅择,并等待對方完成或執(zhí)行其它操作。第一個(gè)不能完成是因?yàn)樗诘却诙€(gè)的完成侣姆。但第二個(gè)也不能完成生真,因?yàn)樗诘却谝粋€(gè)的完成。
Thread Safe 線程安全
線程安全的代碼能在多線程或并發(fā)任務(wù)中被安全的調(diào)用捺宗,而不會導(dǎo)致任何問題(數(shù)據(jù)損壞柱蟀,崩潰,等)蚜厉。線程不安全的代碼在某個(gè)時(shí)刻只能在一個(gè)上下文中運(yùn)行长已。一個(gè)線程安全代碼的例子是 NSDictionary 。你可以在同一時(shí)間在多個(gè)線程中使用它而不會有問題昼牛。另一方面术瓮,NSMutableDictionary 就不是線程安全的,應(yīng)該保證一次只能有一個(gè)線程訪問它贰健。
Context Switch 上下文切換
一個(gè)上下文切換指當(dāng)你在單個(gè)進(jìn)程里切換執(zhí)行不同的線程時(shí)存儲與恢復(fù)執(zhí)行狀態(tài)的過程胞四。這個(gè)過程在編寫多任務(wù)應(yīng)用時(shí)很普遍,但會帶來一些額外的開銷伶椿。
Concurrency vs Parallelism 并發(fā)與并行
并發(fā)和并行通常被一起提到辜伟,所以值得花些時(shí)間解釋它們之間的區(qū)別始藕。
并發(fā)代碼的不同部分可以“同步”執(zhí)行序宦。然而,該怎樣發(fā)生或是否發(fā)生都取決于系統(tǒng)。多核設(shè)備通過并行來同時(shí)執(zhí)行多個(gè)線程舌稀;然而决左,為了使單核設(shè)備也能實(shí)現(xiàn)這一點(diǎn)耘子,它們必須先運(yùn)行一個(gè)線程液南,執(zhí)行一個(gè)上下文切換,然后運(yùn)行另一個(gè)線程或進(jìn)程廊佩。這通常發(fā)生地足夠快以致給我們并發(fā)執(zhí)行地錯(cuò)覺囚聚,如下圖所示:
雖然你可以編寫代碼在 GCD 下并發(fā)執(zhí)行,但 GCD 會決定有多少并行的需求标锄。并行要求并發(fā)顽铸,但并發(fā)并不能保證并行。
更深入的觀點(diǎn)是并發(fā)實(shí)際上是關(guān)于構(gòu)造料皇。當(dāng)你在腦海中用 GCD 編寫代碼谓松,你組織你的代碼來暴露能同時(shí)運(yùn)行的多個(gè)工作片段,以及不能同時(shí)運(yùn)行的那些践剂。如果你想深入此主題鬼譬,看看 this excellent talk by Rob Pike 。
Queues 隊(duì)列
GCD 提供有 dispatch queues 來處理代碼塊逊脯,這些隊(duì)列管理你提供給 GCD 的任務(wù)并用 FIFO 順序執(zhí)行這些任務(wù)优质。這就保證了第一個(gè)被添加到隊(duì)列里的任務(wù)會是隊(duì)列中第一個(gè)開始的任務(wù),而第二個(gè)被添加的任務(wù)將第二個(gè)開始军洼,如此直到隊(duì)列的終點(diǎn)巩螃。
所有的調(diào)度隊(duì)列(dispatch queues)自身都是線程安全的,你能從多個(gè)線程并行的訪問它們匕争。 GCD 的優(yōu)點(diǎn)是顯而易見的避乏,即當(dāng)你了解了調(diào)度隊(duì)列如何為你自己代碼的不同部分提供線程安全。關(guān)于這一點(diǎn)的關(guān)鍵是選擇正確類型的調(diào)度隊(duì)列和正確的調(diào)度函數(shù)來提交你的工作甘桑。
在本節(jié)你會看到兩種調(diào)度隊(duì)列拍皮,都是由 GCD 提供的,然后看一些描述如何用調(diào)度函數(shù)添加工作到隊(duì)列的列子跑杭。
Serial Queues 串行隊(duì)列
這些任務(wù)的執(zhí)行時(shí)機(jī)受到 GCD 的控制铆帽;唯一能確保的事情是 GCD 一次只執(zhí)行一個(gè)任務(wù),并且按照我們添加到隊(duì)列的順序來執(zhí)行德谅。
由于在串行隊(duì)列中不會有兩個(gè)任務(wù)并發(fā)運(yùn)行锄贼,因此不會出現(xiàn)同時(shí)訪問臨界區(qū)的風(fēng)險(xiǎn);相對于這些任務(wù)來說女阀,這就從競態(tài)條件下保護(hù)了臨界區(qū)宅荤。所以如果訪問臨界區(qū)的唯一方式是通過提交到調(diào)度隊(duì)列的任務(wù),那么你就不需要擔(dān)心臨界區(qū)的安全問題了浸策。
Concurrent Queues 并發(fā)隊(duì)列
在并發(fā)隊(duì)列中的任務(wù)能得到的保證是它們會按照被添加的順序開始執(zhí)行冯键,但這就是全部的保證了。任務(wù)可能以任意順序完成庸汗,你不會知道何時(shí)開始運(yùn)行下一個(gè)任務(wù)惫确,或者任意時(shí)刻有多少 Block 在運(yùn)行。再說一遍蚯舱,這完全取決于 GCD 改化。
下圖展示了一個(gè)示例任務(wù)執(zhí)行計(jì)劃,GCD 管理著四個(gè)并發(fā)任務(wù):
注意 Block 1,2 和 3 都立馬開始運(yùn)行枉昏,一個(gè)接一個(gè)陈肛。在 Block 0 開始后,Block 1等待了好一會兒才開始兄裂。同樣句旱, Block 3 在 Block 2 之后才開始,但它先于 Block 2 完成晰奖。
何時(shí)開始一個(gè) Block 完全取決于 GCD 谈撒。如果一個(gè) Block 的執(zhí)行時(shí)間與另一個(gè)重疊,也是由 GCD 來決定是否將其運(yùn)行在另一個(gè)不同的核心上匾南,如果那個(gè)核心可用啃匿,否則就用上下文切換的方式來執(zhí)行不同的 Block 。
有趣的是蛆楞, GCD 提供給你至少五個(gè)特定的隊(duì)列溯乒,可根據(jù)隊(duì)列類型選擇使用。
Queue Types 隊(duì)列類型
首先臊岸,系統(tǒng)提供給你一個(gè)叫做 主隊(duì)列(main queue) 的特殊隊(duì)列橙数。和其它串行隊(duì)列一樣,這個(gè)隊(duì)列中的任務(wù)一次只能執(zhí)行一個(gè)帅戒。然而灯帮,它能保證所有的任務(wù)都在主線程執(zhí)行,而主線程是唯一可用于更新 UI 的線程逻住。這個(gè)隊(duì)列就是用于發(fā)生消息給 UIView 或發(fā)送通知的钟哥。
系統(tǒng)同時(shí)提供給你好幾個(gè)并發(fā)隊(duì)列。它們叫做 全局調(diào)度隊(duì)列(Global Dispatch Queues) 瞎访。目前的四個(gè)全局隊(duì)列有著不同的優(yōu)先級:background腻贰、low、default 以及 high扒秸。要知道播演,Apple 的 API 也會使用這些隊(duì)列冀瓦,所以你添加的任何任務(wù)都不會是這些隊(duì)列中唯一的任務(wù)。
最后写烤,你也可以創(chuàng)建自己的串行隊(duì)列或并發(fā)隊(duì)列翼闽。這就是說,至少有五個(gè)隊(duì)列任你處置:主隊(duì)列洲炊、四個(gè)全局調(diào)度隊(duì)列感局,再加上任何你自己創(chuàng)建的隊(duì)列。
以上是調(diào)度隊(duì)列的大框架暂衡!
dispatch queue分成以下三種:
1)運(yùn)行在主線程的Main queue询微,通過dispatch_get_main_queue獲取。
復(fù)制代碼
/*!
* @function dispatch_get_main_queue
*
* @abstract
* Returns the default queue that is bound to the main thread.
*
* @discussion
* In order to invoke blocks submitted to the main queue, the application must
* call dispatch_main(), NSApplicationMain(), or use a CFRunLoop on the main
* thread.
*
* @result
* Returns the main queue. This queue is created automatically on behalf of
* the main thread before main() is called.
*/
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT struct dispatch_queue_s _dispatch_main_q;
#define dispatch_get_main_queue() \
DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q)
復(fù)制代碼
可以看出狂巢,dispatch_get_main_queue也是一種dispatch_queue_t撑毛。
2)并行隊(duì)列g(shù)lobal dispatch queue,通過dispatch_get_global_queue獲取隧膘,由系統(tǒng)創(chuàng)建三個(gè)不同優(yōu)先級的dispatch queue代态。并行隊(duì)列的執(zhí)行順序與其加入隊(duì)列的順序相同。
3)串行隊(duì)列serial queues一般用于按順序同步訪問疹吃,可創(chuàng)建任意數(shù)量的串行隊(duì)列蹦疑,各個(gè)串行隊(duì)列之間是并發(fā)的。
當(dāng)想要任務(wù)按照某一個(gè)特定的順序執(zhí)行時(shí)萨驶,串行隊(duì)列是很有用的歉摧。串行隊(duì)列在同一個(gè)時(shí)間只執(zhí)行一個(gè)任務(wù)。我們可以使用串行隊(duì)列代替鎖去保護(hù)共享的數(shù)據(jù)腔呜。和鎖不同叁温,一個(gè)串行隊(duì)列可以保證任務(wù)在一個(gè)可預(yù)知的順序下執(zhí)行。
serial queues通過dispatch_queue_create創(chuàng)建核畴,可以使用函數(shù)dispatch_retain和dispatch_release去增加或者減少引用計(jì)數(shù)膝但。
GCD的用法:
復(fù)制代碼
//? 后臺執(zhí)行:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// something
});
// 主線程執(zhí)行:
dispatch_async(dispatch_get_main_queue(), ^{
// something
});
// 一次性執(zhí)行:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
// 延遲2秒執(zhí)行:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// code to be executed on the main queue after delay
});
// 自定義dispatch_queue_t
dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL);
dispatch_async(urls_queue, ^{
// your code
});
dispatch_release(urls_queue);
// 合并匯總結(jié)果
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
// 并行執(zhí)行的線程一
});
dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
// 并行執(zhí)行的線程二
});
dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{
// 匯總結(jié)果
});
復(fù)制代碼
一個(gè)應(yīng)用GCD的例子:
復(fù)制代碼
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL * url = [NSURL URLWithString:@"http://www.baidu.com"];
NSError * error;
NSString * data = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if (data != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"call back, the data is: %@", data);
});
} else {
NSLog(@"error when download:%@", error);
}
});
復(fù)制代碼
Dispatch Groups(調(diào)度組)
Dispatch Group 會在整個(gè)組的任務(wù)都完成時(shí)通知你。這些任務(wù)可以是同步的谤草,也可以是異步的跟束,即便在不同的隊(duì)列也行。而且在整個(gè)組的任務(wù)都完成時(shí)丑孩,Dispatch Group 可以用同步的或者異步的方式通知你冀宴。因?yàn)橐O(jiān)控的任務(wù)在不同隊(duì)列,那就用一個(gè) dispatch_group_t 的實(shí)例來記下這些不同的任務(wù)温学。
當(dāng)組中所有的事件都完成時(shí)略贮,GCD 的 API 提供了兩種通知方式。
第一種是 dispatch_group_wait ,它會阻塞當(dāng)前線程逃延,直到組里面所有的任務(wù)都完成或者等到某個(gè)超時(shí)發(fā)生览妖。這恰好是你目前所需要的。
打開 PhotoManager.m真友,用下列實(shí)現(xiàn)替換 downloadPhotosWithCompletionBlock:
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // 1
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create(); // 2
for (NSInteger i = 0; i < 3; i++) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup); // 3
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup); // 4
}];
[[PhotoManager sharedManager] addPhoto:photo];
}
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
dispatch_async(dispatch_get_main_queue(), ^{ // 6
if (completionBlock) { // 7
completionBlock(error);
}
});
});
}
按照注釋的順序黄痪,你會看到:
1. 因?yàn)槟阍谑褂玫氖峭降?dispatch_group_wait ,它會阻塞當(dāng)前線程盔然,所以你要用 dispatch_async 將整個(gè)方法放入后臺隊(duì)列以避免阻塞主線程。
2. 創(chuàng)建一個(gè)新的 Dispatch Group是嗜,它的作用就像一個(gè)用于未完成任務(wù)的計(jì)數(shù)器愈案。
3. dispatch_group_enter 手動(dòng)通知 Dispatch Group 任務(wù)已經(jīng)開始。你必須保證 dispatch_group_enter 和 dispatch_group_leave 成對出現(xiàn)鹅搪,否則你可能會遇到詭異的崩潰問題站绪。
4. 手動(dòng)通知 Group 它的工作已經(jīng)完成。再次說明丽柿,你必須要確保進(jìn)入 Group 的次數(shù)和離開 Group 的次數(shù)相等恢准。
5. dispatch_group_wait 會一直等待,直到任務(wù)全部完成或者超時(shí)甫题。如果在所有任務(wù)完成前超時(shí)了馁筐,該函數(shù)會返回一個(gè)非零值。你可以對此返回值做條件判斷以確定是否超出等待周期坠非;然而敏沉,你在這里用 DISPATCH_TIME_FOREVER 讓它永遠(yuǎn)等待。它的意思炎码,勿庸置疑就是盟迟,永-遠(yuǎn)-等-待!這樣很好潦闲,因?yàn)閳D片的創(chuàng)建工作總是會完成的攒菠。
6. 此時(shí)此刻,你已經(jīng)確保了歉闰,要么所有的圖片任務(wù)都已完成辖众,要么發(fā)生了超時(shí)。然后新娜,你在主線程上運(yùn)行 completionBlock 回調(diào)赵辕。這會將工作放到主線程上,并在稍后執(zhí)行概龄。
7. 最后还惠,檢查 completionBlock 是否為 nil,如果不是私杜,那就運(yùn)行它蚕键。
編譯并運(yùn)行你的應(yīng)用救欧,嘗試下載多個(gè)圖片,觀察你的應(yīng)用是在何時(shí)運(yùn)行 completionBlock 的锣光。
注意:如果你是在真機(jī)上運(yùn)行應(yīng)用笆怠,而且網(wǎng)絡(luò)活動(dòng)發(fā)生得太快以致難以觀察 completionBlock 被調(diào)用的時(shí)刻,那么你可以在 Settings 應(yīng)用里的開發(fā)者相關(guān)部分里打開一些網(wǎng)絡(luò)設(shè)置誊爹,以確保代碼按照我們所期望的那樣工作蹬刷。只需去往 Network Link Conditioner 區(qū),開啟它频丘,再選擇一個(gè) Profile办成,“Very Bad Network” 就不錯(cuò)。
如果你是在模擬器里運(yùn)行應(yīng)用搂漠,你可以使用 來自 GitHub 的 Network Link Conditioner 來改變網(wǎng)絡(luò)速度迂卢。它會成為你工具箱中的一個(gè)好工具,因?yàn)樗鼜?qiáng)制你研究你的應(yīng)用在連接速度并非最佳的情況下會變成什么樣桐汤。
目前為止的解決方案還不錯(cuò)而克,但是總體來說,如果可能怔毛,最好還是要避免阻塞線程员萍。你的下一個(gè)任務(wù)是重寫一些方法,以便當(dāng)所有下載任務(wù)完成時(shí)能異步通知你馆截。
在我們轉(zhuǎn)向另外一種使用 Dispatch Group 的方式之前充活,先看一個(gè)簡要的概述,關(guān)于何時(shí)以及怎樣使用有著不同的隊(duì)列類型的 Dispatch Group :
1. 自定義串行隊(duì)列:它很適合當(dāng)一組任務(wù)完成時(shí)發(fā)出通知蜡娶。
2. 主隊(duì)列(串行):它也很適合這樣的情況混卵。但如果你要同步地等待所有工作地完成,那你就不應(yīng)該使用它窖张,因?yàn)槟悴荒茏枞骶€程幕随。然而,異步模型是一個(gè)很有吸引力的能用于在幾個(gè)較長任務(wù)(例如網(wǎng)絡(luò)調(diào)用)完成后更新 UI 的方式宿接。
3. 并發(fā)隊(duì)列:它也很適合 Dispatch Group 和完成時(shí)通知赘淮。
Dispatch Group,第二種方式
上面的一切都很好睦霎,但在另一個(gè)隊(duì)列上異步調(diào)度然后使用 dispatch_group_wait 來阻塞實(shí)在顯得有些笨拙梢卸。是的,還有另一種方式……
在 PhotoManager.m 中找到 downloadPhotosWithCompletionBlock: 方法副女,用下面的實(shí)現(xiàn)替換它:
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
// 1
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup); // 2
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup); // 3
}];
[[PhotoManager sharedManager] addPhoto:photo];
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ // 4
if (completionBlock) {
completionBlock(error);
}
});
}
下面解釋新的異步方法如何工作:
1. 在新的實(shí)現(xiàn)里蛤高,因?yàn)槟銢]有阻塞主線程,所以你并不需要將方法包裹在 async 調(diào)用中。
2. 同樣的 enter 方法戴陡,沒做任何修改塞绿。
3. 同樣的 leave 方法,也沒做任何修改恤批。
4. dispatch_group_notify 以異步的方式工作异吻。當(dāng) Dispatch Group 中沒有任何任務(wù)時(shí),它就會執(zhí)行其代碼喜庞,那么 completionBlock 便會運(yùn)行诀浪。你還指定了運(yùn)行 completionBlock 的隊(duì)列,此處赋荆,主隊(duì)列就是你所需要的笋妥。
對于這個(gè)特定的工作,上面的處理明顯更清晰窄潭,而且也不會阻塞任何線程。
A B C D 4個(gè)并發(fā)下載任務(wù)酵颁,怎樣在第一時(shí)間知道任務(wù)全部完成嫉你?
dispatch_group 可以幫我們實(shí)現(xiàn)這樣的控制。
上代碼躏惋,看說明.
[objc] view plain copy
dispatch_group_t group = dispatch_group_create();
// 某個(gè)任務(wù)放進(jìn) group
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 任務(wù)代碼1
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 任務(wù)代碼2
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 任務(wù)全部完成處理
NSLog(@"isover");
});
創(chuàng)建一個(gè)任務(wù)組幽污,然后將異步操作放進(jìn)組里面,在最后用notify 告知所有任務(wù)完成簿姨,并做相應(yīng)處理距误,一般來說都是在主線程里面刷新UI來提示用戶了。你如果不依賴UI放進(jìn)子線程里面也是沒有問題的扁位。當(dāng)然group同步的方式還有其他
[objc] view plain copy
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i ++)
{
dispatch_group_enter(group);
// 任務(wù)代碼i 假定任務(wù) 是異步執(zhí)行block回調(diào)
// block 回調(diào)執(zhí)行
dispatch_group_leave(group);
// block 回調(diào)執(zhí)行
}
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
// 主線程處理
});
首先我們異步執(zhí)行准潭,因?yàn)閐ispatch_group_wait函數(shù)是阻塞的,for里面安排了三個(gè)任務(wù)域仇,這三個(gè)任務(wù)都是加載刑然,在任務(wù)開始前 調(diào)用 enter,任務(wù)完成時(shí)調(diào)用leave暇务,wait函數(shù)一直阻塞泼掠,直到它發(fā)現(xiàn)group里面的任務(wù)全部leave,它才放棄阻塞(任務(wù)全部完成),然后我們在主線程更新UI告知用戶
7.3垦细、定時(shí)器
大多數(shù)情況下择镇,對于定時(shí)事件,你會選擇NSTimer括改。定時(shí)器的GCD版本是底層的腻豌,它會給你更多控制權(quán)——但要小心使用。
需要特別重點(diǎn)指出的是,為了讓OS節(jié)省電量饲梭,需要為GCD的定時(shí)器接口指定一個(gè)低的誤差值乘盖。如果你不必要的指定了一個(gè)過低的誤差值,你將會浪費(fèi)更多的電量憔涉。
這里我們設(shè)定了一個(gè)5秒的定時(shí)器订框,并允許有十分之一秒的誤差:
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
0, 0, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(source, ^(){
NSLog(@"Time flies.");
});
dispatch_time_t start
dispatch_source_set_timer(source, DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC,
100ull * NSEC_PER_MSEC);
self.source = source;
dispatch_resume(self.source);