《編寫高質(zhì)量iOS與OS X代碼的52個(gè)有效方法》--第六章 第44條
(ps:此乃讀書筆記,加深記憶,僅供大家參考)
第44條:通過Dispatch Group機(jī)制,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務(wù)
dispatch group是GCD的一項(xiàng)特性,能夠把任務(wù)分組。調(diào)用者可以等待這組任務(wù)執(zhí)行完畢,也可以在提供回調(diào)函數(shù)之后繼續(xù)往下執(zhí)行,這組任務(wù)完成時(shí),調(diào)用者會(huì)得到通知。這個(gè)功能有許多用途,其中最重要、最值得注意的用法,就是把將要并發(fā)執(zhí)行的多個(gè)任務(wù)合為一組,于是調(diào)用者就可以知道這些任務(wù)何時(shí)才能全部執(zhí)行完畢。
下面這個(gè)函數(shù)可以創(chuàng)建dispatch group:
dispatch_group_t group = dispatch_group_create();
dispatch group就是個(gè)簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),這種結(jié)構(gòu)彼此之間沒什么區(qū)別,它不像派發(fā)隊(duì)列,后者還有個(gè)用來區(qū)別身份的標(biāo)識(shí)符。想把任務(wù)編組,有兩種辦法。第一種是下面這個(gè)函數(shù):
void dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
它是普通的dispatch_async函數(shù)的變體,比原來多一個(gè)函數(shù),用于表示待執(zhí)行的塊所歸屬的組。還有種辦法能夠指定任務(wù)所屬的dispatch group,那就是下面這一對(duì)函數(shù):
dispatch_group_enter(dispatch_group_t group)
dispatch_group_leave(dispatch_group_t group)
前者能夠使分組里正要執(zhí)行的任務(wù)數(shù)遞增,而后者則使之遞減。由此可知,調(diào)用了dispatch_group_enter以后,必須又與之對(duì)應(yīng)的dispatch_group_leave才行。這與引用計(jì)數(shù)(參見第29條)相似,要使用引用計(jì)數(shù)。就必須令保留操作與釋放操作彼此對(duì)應(yīng),以防內(nèi)存泄漏。
下面這個(gè)函數(shù)可用于等待dispatch group執(zhí)行完畢:
void dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)
此函數(shù)接受兩個(gè)參數(shù),一個(gè)是要等待的group,另一個(gè)是代表等待時(shí)間的timeout值。timeout參數(shù)表示函數(shù)在等待dispatch group執(zhí)行完畢時(shí),應(yīng)該阻塞多久。如果執(zhí)行dispatch group所需的時(shí)間小于timeout,則返回0,否則返回非0值。此參數(shù)也可以取常量DISPATCH_TIME_FOREVER,這表示函數(shù)會(huì)一直等待dispatch group執(zhí)行完,而不會(huì)超時(shí)(time out)。
除了可以用上面那個(gè)函數(shù)等待dispatch group執(zhí)行完畢之外,也可以換個(gè)辦法,使用下列函數(shù):
void dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
與wait函數(shù)略有不同的是:開發(fā)者可以向此函數(shù)傳入塊,等dispatch group執(zhí)行完畢之后,塊會(huì)在特定的線程上執(zhí)行。加入當(dāng)前此案成不應(yīng)阻塞,而開發(fā)者又想在那些任務(wù)全部完成時(shí)得到通知,那么此做法就很有必要了。
如果想令數(shù)組中的每個(gè)對(duì)象都執(zhí)行某項(xiàng)任務(wù),并且想等待所有任務(wù)執(zhí)行完畢,那么就可以使用這個(gè)GCD特性來實(shí)現(xiàn)。代碼如下:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
NSArray * collection;
for (id object in collection) {
dispatch_group_async(dispatchGroup, queue, ^{
[object description];
});
}
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
//Continue processing after completing tasks
若當(dāng)前線程不應(yīng)阻塞,則可用notify函數(shù)來取代wait:
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
//Continue processing after completing tasks
});
notify回調(diào)時(shí)所選用的隊(duì)列,完全應(yīng)該根據(jù)具體情況來定。筆者在范例代碼中使用了主線程隊(duì)列,這種是常見寫法。也可以用自定義的串行隊(duì)列或者全局并發(fā)隊(duì)列。
在本例中,所有任務(wù)都派發(fā)到同一隊(duì)列之中。但實(shí)際上未必一定要這樣做。也可以把某些任務(wù)放在優(yōu)先級(jí)高的線程上執(zhí)行,同時(shí)仍然把所有任務(wù)都?xì)w入同一個(gè)dispatch group,并在執(zhí)行完畢時(shí)獲得通知:
dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
NSArray * lowPriorityObject;
NSArray * highPriorityObject;
for (id object in lowPriorityObject) {
dispatch_group_async(dispatchGroup, lowPriorityQueue, ^{
[object description];
});
}
for (id object in highPriorityObject) {
dispatch_group_async(dispatchGroup, highPriorityQueue, ^{
[object description];
});
}
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
//Continue processing after completing tasks
});
除了像上面這樣把任務(wù)提交到并發(fā)隊(duì)列之外,也可以把任務(wù)提交至各個(gè)串行隊(duì)列中,并用dispatch group跟蹤其執(zhí)行狀況。然而,如果所有任務(wù)都排在同一個(gè)串行隊(duì)列里面,那么dispatch group就用處不大了。因?yàn)榇藭r(shí),任務(wù)總要逐個(gè)執(zhí)行,所以只需在提交完全部任務(wù)之后再提交一個(gè)塊即可,這樣做與通過notify函數(shù)等待dispatch group執(zhí)行完畢后再回調(diào)塊是等效的:
dispatch_queue_t queue = dispatch_queue_create("com.effectiveobjectivec.queue", NULL);
NSArray *collection;
for (id object in collection) {
dispatch_async(queue, ^{
[object description];
});
}
dispatch_async(queue, ^{
//Continue processing after completing tasks
});
筆者為何要在標(biāo)題中談到“根據(jù)系統(tǒng)資源狀況來執(zhí)行任務(wù)”呢?回頭看看向并發(fā)隊(duì)列派發(fā)任務(wù)的那個(gè)例子,就會(huì)明白了。為了執(zhí)行隊(duì)列中的塊,GCD會(huì)在適當(dāng)?shù)臅r(shí)機(jī)自動(dòng)創(chuàng)建新線程或或復(fù)用舊線程。如果并發(fā)隊(duì)列,那么其中有可能會(huì)有多個(gè)線程,這也就意味著多個(gè)塊可以并發(fā)執(zhí)行。在并發(fā)隊(duì)列中,執(zhí)行任務(wù)所用的并發(fā)線程數(shù)量,取決于各個(gè)因素,而GCD主要是根據(jù)系統(tǒng)資源狀況來判定這些因素的。加入CPU有多個(gè)核心,并且隊(duì)列中有大量任務(wù)等待執(zhí)行,那那么GCD就可能會(huì)給該隊(duì)列配備多個(gè)線程。通過dispatch group所提供的這種簡(jiǎn)便方式,既可以并發(fā)執(zhí)行一系列給定的任務(wù),又能在全部任務(wù)結(jié)束時(shí)得到通知。由于GCD有并發(fā)隊(duì)列機(jī)制,所以能夠根據(jù)可用的系統(tǒng)資源狀況來并發(fā)執(zhí)行任務(wù)。
在前面的范例代碼中,我們遍歷某個(gè)collection,并在其每個(gè)元素上執(zhí)行任務(wù),而這也可以用另外一個(gè)GCD函數(shù)來實(shí)現(xiàn):
void dispatch_apply(size_t iterations, dispatch_queue_t queue,void (^block)(size_t));
此函數(shù)將塊反復(fù)執(zhí)行一定的次數(shù),每次傳給塊的參數(shù)值都會(huì)遞增,從0開始,直至“iterations-1”。其用法如下:
dispatch_queue_t queue = dispatch_queue_create("com.effectiveobjectivec.queue", NULL);
dispatch_apply(10, queue, ^(size_t i) {
//Perform task
});
有一件事要注意:dispatch_apply所用的隊(duì)列可以是并發(fā)隊(duì)列。如果采用并發(fā)隊(duì)列,那么系統(tǒng)就可以根據(jù)資源狀況來并行執(zhí)行這些塊了,這與使用dispatch group的那段范例代碼一樣。
這再次表明,未必總要使用dispatch group。然而,dispatch_apply會(huì)持續(xù)阻塞,直到所有任務(wù)都執(zhí)行完畢為止。由此可見,假如把塊派給了當(dāng)前隊(duì)列(或者體系中高于當(dāng)前隊(duì)列的某個(gè)串行隊(duì)列),就將導(dǎo)致死鎖。若想在后臺(tái)執(zhí)行任務(wù),則應(yīng)使用dispatch group。
要點(diǎn)
- 一系列任務(wù)可歸入一個(gè)dispatch group之中。開發(fā)者可以在這組任務(wù)執(zhí)行完畢時(shí)獲得通知。
- 通過dispatch group,可以在并發(fā)式派發(fā)隊(duì)列里同時(shí)執(zhí)行多項(xiàng)任務(wù)。此時(shí),GCD會(huì)根據(jù)系統(tǒng)資源狀況來調(diào)度這些并發(fā)執(zhí)行的任務(wù)。開發(fā)者若自己來實(shí)現(xiàn)此功能,則需編寫大量代碼。