原文鏈接:http://www.cocoachina.com/ios/20150807/12911.html
現(xiàn)如今移動(dòng)設(shè)備也早已經(jīng)進(jìn)入了多核心 CPU 時(shí)代帖世,并且隨著時(shí)間的推移取董,CPU 的核心數(shù)只會(huì)增加不會(huì)減少棍苹。而作為軟件開發(fā)者,我們需要做的就是盡可能地提高應(yīng)用的并發(fā)性甲葬,來充分利用這些多核心 CPU 的性能廊勃。在 iOS 開發(fā)中,我們主要可以通過 Operation Queues经窖、Dispatch Queues 和 Dispatch Sources 來提高應(yīng)用的并發(fā)性坡垫。本文將主要介紹 Operation Queues 的相關(guān)知識(shí),另外兩個(gè)屬于 Grand Central Dispatch(以下正文簡(jiǎn)稱 GCD )的范疇画侣,將會(huì)在后續(xù)的文章中進(jìn)行介紹冰悠。
由于本文涉及的內(nèi)容較多,所以建議讀者先提前了解一下本文的目錄結(jié)構(gòu)配乱,以便對(duì)本文有一個(gè)宏觀的認(rèn)識(shí):
基本概念
術(shù)語
串行 vs. 并發(fā)
同步 vs. 異步
隊(duì)列 vs. 線程
iOS 的并發(fā)編程模型
Operation Queues vs. Grand Central Dispatch (GCD)
關(guān)于 Operation 對(duì)象
并發(fā) vs. 非并發(fā) Operation
創(chuàng)建 NSInvocationOperation 對(duì)象
創(chuàng)建 NSBlockOperation 對(duì)象
自定義 Operation 對(duì)象
執(zhí)行主任務(wù)
響應(yīng)取消事件
配置并發(fā)執(zhí)行的 Operation
維護(hù) KVO 通知
定制 Operation 對(duì)象的執(zhí)行行為
配置依賴關(guān)系
修改 Operation 在隊(duì)列中的優(yōu)先級(jí)
修改 Operation 執(zhí)行任務(wù)線程的優(yōu)先級(jí)
設(shè)置 Completion Block
執(zhí)行 Operation 對(duì)象
添加 Operation 到 Operation Queue 中
手動(dòng)執(zhí)行 Operation
取消 Operation
等待 Operation 執(zhí)行完成
暫停和恢復(fù) Operation Queue
總結(jié)
基本概念
在正式開始介紹 Operation Queues 的相關(guān)知識(shí)前溉卓,我想先介紹幾個(gè)在 iOS 并發(fā)編程中非常容易混淆的基本概念皮迟,以幫助讀者更好地理解本文。注桑寨,本文中的 Operation Queues 指的是 NSOperation 和 NSOperationQueue 的統(tǒng)稱伏尼。
術(shù)語
首先,我們先來了解一下在 iOS 并發(fā)編程中非常重要的三個(gè)術(shù)語尉尾,這是我們理解 iOS 并發(fā)編程的基礎(chǔ):
進(jìn)程(process)爆阶,指的是一個(gè)正在運(yùn)行中的可執(zhí)行文件。每一個(gè)進(jìn)程都擁有獨(dú)立的虛擬內(nèi)存空間和系統(tǒng)資源沙咏,包括端口權(quán)限等辨图,且至少包含一個(gè)主線程和任意數(shù)量的輔助線程。另外肢藐,當(dāng)一個(gè)進(jìn)程的主線程退出時(shí)故河,這個(gè)進(jìn)程就結(jié)束了;
線程(thread)吆豹,指的是一個(gè)獨(dú)立的代碼執(zhí)行路徑鱼的,也就是說線程是代碼執(zhí)行路徑的最小分支。在 iOS 中瞻讽,線程的底層實(shí)現(xiàn)是基于 POSIX threads API 的鸳吸,也就是我們常說的 pthreads ;
任務(wù)(task)速勇,指的是我們需要執(zhí)行的工作晌砾,是一個(gè)抽象的概念,用通俗的話說烦磁,就是一段代碼养匈。
串行 vs. 并發(fā)
從本質(zhì)上來說,串行和并發(fā)的主要區(qū)別在于允許同時(shí)執(zhí)行的任務(wù)數(shù)量都伪。串行呕乎,指的是一次只能執(zhí)行一個(gè)任務(wù),必須等一個(gè)任務(wù)執(zhí)行完成后才能執(zhí)行下一個(gè)任務(wù)陨晶;并發(fā)猬仁,則指的是允許多個(gè)任務(wù)同時(shí)執(zhí)行。
同步 vs. 異步
同樣的先誉,同步和異步操作的主要區(qū)別在于是否等待操作執(zhí)行完成湿刽,亦即是否阻塞當(dāng)前線程。同步操作會(huì)等待操作執(zhí)行完成后再繼續(xù)執(zhí)行接下來的代碼褐耳,而異步操作則恰好相反诈闺,它會(huì)在調(diào)用后立即返回,不會(huì)等待操作的執(zhí)行結(jié)果铃芦。
隊(duì)列 vs. 線程
有一些對(duì) iOS 并發(fā)編程模型不太了解的同學(xué)可能會(huì)對(duì)隊(duì)列和線程產(chǎn)生混淆雅镊,不清楚它們之間的區(qū)別與聯(lián)系襟雷,因此,我覺得非常有必要在這里簡(jiǎn)單地介紹一下仁烹。在 iOS 中耸弄,有兩種不同類型的隊(duì)列,分別是串行隊(duì)列和并發(fā)隊(duì)列晃危。正如我們上面所說的叙赚,串行隊(duì)列一次只能執(zhí)行一個(gè)任務(wù),而并發(fā)隊(duì)列則可以允許多個(gè)任務(wù)同時(shí)執(zhí)行僚饭。iOS 系統(tǒng)就是使用這些隊(duì)列來進(jìn)行任務(wù)調(diào)度的,它會(huì)根據(jù)調(diào)度任務(wù)的需要和系統(tǒng)當(dāng)前的負(fù)載情況動(dòng)態(tài)地創(chuàng)建和銷毀線程胧砰,而不需要我們手動(dòng)地管理鳍鸵。
iOS 的并發(fā)編程模型
在其他許多語言中,為了提高應(yīng)用的并發(fā)性尉间,我們往往需要自行創(chuàng)建一個(gè)或多個(gè)額外的線程偿乖,并且手動(dòng)地管理這些線程的生命周期,這本身就已經(jīng)是一項(xiàng)非常具有挑戰(zhàn)性的任務(wù)了哲嘲。此外贪薪,對(duì)于一個(gè)應(yīng)用來說,最優(yōu)的線程個(gè)數(shù)會(huì)隨著系統(tǒng)當(dāng)前的負(fù)載和低層硬件的情況發(fā)生動(dòng)態(tài)變化眠副。因此画切,一個(gè)單獨(dú)的應(yīng)用想要實(shí)現(xiàn)一套正確的多線程解決方案就變成了一件幾乎不可能完成的事情。而更糟糕的是囱怕,線程的同步機(jī)制大幅度地增加了應(yīng)用的復(fù)雜性霍弹,并且還存在著不一定能夠提高應(yīng)用性能的風(fēng)險(xiǎn)。
然而娃弓,值得慶幸的是典格,在 iOS 中,蘋果采用了一種比傳統(tǒng)的基于線程的系統(tǒng)更加異步的方式來執(zhí)行并發(fā)任務(wù)台丛。與直接創(chuàng)建線程的方式不同耍缴,我們只需定義好要調(diào)度的任務(wù),然后讓系統(tǒng)幫我們?nèi)?zhí)行這些任務(wù)就可以了挽霉。我們可以完全不需要關(guān)心線程的創(chuàng)建與銷毀防嗡、以及多線程之間的同步等問題,蘋果已經(jīng)在系統(tǒng)層面幫我們處理好了炼吴,并且比我們手動(dòng)地管理這些線程要高效得多本鸣。
因此,我們應(yīng)該要聽從蘋果的勸告硅蹦,珍愛生命荣德,遠(yuǎn)離線程闷煤。不過話又說回來,盡管隊(duì)列是執(zhí)行并發(fā)任務(wù)的首先方式涮瞻,但是畢竟它們也不是什么萬能的靈丹妙藥鲤拿。所以,在以下三種場(chǎng)景下署咽,我們還是應(yīng)該直接使用線程的:
用線程以外的其他任何方式都不能實(shí)現(xiàn)我們的特定任務(wù)近顷;
必須實(shí)時(shí)執(zhí)行一個(gè)任務(wù)。因?yàn)殡m然隊(duì)列會(huì)盡可能快地執(zhí)行我們提交的任務(wù)宁否,但是并不能保證實(shí)時(shí)性窒升;
你需要對(duì)在后臺(tái)執(zhí)行的任務(wù)有更多的可預(yù)測(cè)行為。
Operation Queues vs. Grand Central Dispatch (GCD)
簡(jiǎn)單來說慕匠,GCD 是蘋果基于 C 語言開發(fā)的饱须,一個(gè)用于多核編程的解決方案,主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對(duì)稱多處理系統(tǒng)台谊。而 Operation Queues 則是一個(gè)建立在 GCD 的基礎(chǔ)之上的蓉媳,面向?qū)ο蟮慕鉀Q方案。它使用起來比 GCD 更加靈活锅铅,功能也更加強(qiáng)大酪呻。下面簡(jiǎn)單地介紹了 Operation Queues 和 GCD 各自的使用場(chǎng)景:
Operation Queues :相對(duì) GCD 來說,使用 Operation Queues 會(huì)增加一點(diǎn)點(diǎn)額外的開銷盐须,但是我們卻換來了非常強(qiáng)大的靈活性和功能玩荠,我們可以給 operation 之間添加依賴關(guān)系、取消一個(gè)正在執(zhí)行的 operation 丰歌、暫停和恢復(fù) operation queue 等姨蟋;
GCD :則是一種更輕量級(jí)的,以 FIFO 的順序執(zhí)行并發(fā)任務(wù)的方式立帖,使用 GCD 時(shí)我們并不關(guān)心任務(wù)的調(diào)度情況眼溶,而讓系統(tǒng)幫我們自動(dòng)處理。但是 GCD 的短板也是非常明顯的晓勇,比如我們想要給任務(wù)之間添加依賴關(guān)系堂飞、取消或者暫停一個(gè)正在執(zhí)行的任務(wù)時(shí)就會(huì)變得非常棘手。
關(guān)于 Operation 對(duì)象
在 iOS 開發(fā)中绑咱,我們可以使用 NSOperation 類來封裝需要執(zhí)行的任務(wù)绰筛,而一個(gè) operation 對(duì)象(以下正文簡(jiǎn)稱 operation )指的就是 NSOperation 類的一個(gè)具體實(shí)例。NSOperation 本身是一個(gè)抽象類描融,不能直接實(shí)例化铝噩,因此,如果我們想要使用它來執(zhí)行具體任務(wù)的話窿克,就必須創(chuàng)建自己的子類或者使用系統(tǒng)預(yù)定義的兩個(gè)子類骏庸,NSInvocationOperation 和 NSBlockOperation 毛甲。
NSInvocationOperation :我們可以通過一個(gè) object 和 selector 非常方便地創(chuàng)建一個(gè) NSInvocationOperation ,這是一種非常動(dòng)態(tài)和靈活的方式具被。假設(shè)我們已經(jīng)有了一個(gè)現(xiàn)成的方法玻募,這個(gè)方法中的代碼正好就是我們需要執(zhí)行的任務(wù),那么我們就可以在不修改任何現(xiàn)有代碼的情況下一姿,通過方法所在的對(duì)象和這個(gè)現(xiàn)有方法直接創(chuàng)建一個(gè) NSInvocationOperation 七咧。
NSBlockOperation :我們可以使用 NSBlockOperation 來并發(fā)執(zhí)行一個(gè)或多個(gè) block ,只有當(dāng)一個(gè) NSBlockOperation 所關(guān)聯(lián)的所有 block 都執(zhí)行完畢時(shí)叮叹,這個(gè) NSBlockOperation 才算執(zhí)行完成艾栋,有點(diǎn)類似于 dispatch_group 的概念。
另外衬横,所有的 operation 都支持以下特性:
支持在 operation 之間建立依賴關(guān)系裹粤,只有當(dāng)一個(gè) operation 所依賴的所有 operation 都執(zhí)行完成時(shí),這個(gè) operation 才能開始執(zhí)行蜂林;
支持一個(gè)可選的 completion block ,這個(gè) block 將會(huì)在 operation 的主任務(wù)執(zhí)行完成時(shí)被調(diào)用拇泣;
支持通過 KVO 來觀察 operation 執(zhí)行狀態(tài)的變化噪叙;
支持設(shè)置執(zhí)行的優(yōu)先級(jí),從而影響 operation 之間的相對(duì)執(zhí)行順序霉翔;
支持取消操作睁蕾,可以允許我們停止正在執(zhí)行的 operation 。
并發(fā) vs. 非并發(fā) Operation
通常來說债朵,我們都是通過將 operation 添加到一個(gè) operation queue 的方式來執(zhí)行 operation 的子眶,然而這并不是必須的。我們也可以直接通過調(diào)用 start 方法來執(zhí)行一個(gè) operation 序芦,但是這種方式并不能保證 operation 是異步執(zhí)行的臭杰。NSOperation 類的 isConcurrent 方法的返回值標(biāo)識(shí)了一個(gè) operation 相對(duì)于調(diào)用它的 start 方法的線程來說是否是異步執(zhí)行的。在默認(rèn)情況下谚中,isConcurrent 方法的返回值是 NO 渴杆,也就是說會(huì)阻塞調(diào)用它的 start 方法的線程。
如果我們想要自定義一個(gè)并發(fā)執(zhí)行的 operation 宪塔,那么我們就必須要編寫一些額外的代碼來讓這個(gè) operation 異步執(zhí)行磁奖。比如,為這個(gè) operation 創(chuàng)建新的線程某筐、調(diào)用系統(tǒng)的異步方法或者其他任何方式來確保 start 方法在開始執(zhí)行任務(wù)后立即返回比搭。
在絕大多數(shù)情況下,我們都不需要去實(shí)現(xiàn)一個(gè)并發(fā)的 operation 南誊。如果我們一直是通過將 operation 添加到 operation queue 的方式來執(zhí)行 operation 的話身诺,我們就完全沒有必要去實(shí)現(xiàn)一個(gè)并發(fā)的 operation 蜜托。因?yàn)椋?dāng)我們將一個(gè)非并發(fā)的 operation 添加到 operation queue 后戚长,operation queue 會(huì)自動(dòng)為這個(gè) operation 創(chuàng)建一個(gè)線程盗冷。因此,只有當(dāng)我們需要手動(dòng)地執(zhí)行一個(gè) operation 同廉,又想讓它異步執(zhí)行時(shí)仪糖,我們才有必要去實(shí)現(xiàn)一個(gè)并發(fā)的 operation 。
創(chuàng)建 NSInvocationOperation 對(duì)象
正如上面提到的迫肖,NSInvocationOperation 是 NSOperation 類的一個(gè)子類锅劝,當(dāng)一個(gè) NSInvocationOperation 開始執(zhí)行時(shí),它會(huì)調(diào)用我們指定的 object 的 selector 方法蟆湖。通過使用 NSInvocationOperation 類故爵,我們可以避免為每一個(gè)任務(wù)都創(chuàng)建一個(gè)自定義的子類,特別是當(dāng)我們?cè)谛薷囊粋€(gè)已經(jīng)存在的應(yīng)用隅津,并且這個(gè)應(yīng)用中已經(jīng)有了我們需要執(zhí)行的任務(wù)所對(duì)應(yīng)的 object 和 selector 時(shí)非常有用诬垂。
下面的示例代碼展示了如何通過 object 和 selector 創(chuàng)建一個(gè) NSInvocationOperation 對(duì)象。說明伦仍,本文中的所有示例代碼都可以在這里 OperationQueues 找到结窘,每一個(gè)類都有與之對(duì)應(yīng)的測(cè)試類,充當(dāng) client 的角色充蓝,建議你在看完一個(gè)小節(jié)的代碼時(shí)隧枫,運(yùn)行一下相應(yīng)的測(cè)試用例,觀察打印的結(jié)果谓苟,以加深理解官脓。
1
2
3
4
5
6
7
8
9
10
@implementation OQCreateInvocationOperation
- (NSInvocationOperation *)invocationOperationWithData:(id)data {
return [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myTaskMethod1:) object:data];
}
- (void)myTaskMethod1:(id)data {
NSLog(@"Start executing %@ with data: %@, mainThread: %@, currentThread: %@", NSStringFromSelector(_cmd), data, [NSThread mainThread], [NSThread currentThread]);
sleep(3);
NSLog(@"Finish executing %@", NSStringFromSelector(_cmd));
}
@end
另外,我們?cè)谇懊嬉蔡岬搅死员海琋SInvocationOperation 類的使用可以非常的動(dòng)態(tài)和靈活卑笨,其中比較顯著的一點(diǎn)就是我們可以根據(jù)上下文動(dòng)態(tài)地調(diào)用 object 的不同 selector 。比如說纱皆,我們可以根據(jù)用戶的輸入動(dòng)態(tài)地執(zhí)行不同的 selector :
1
2
3
4
5
6
7
8
9
10
11
12
- (NSInvocationOperation *)invocationOperationWithData:(id)data userInput:(NSString *)userInput {
NSInvocationOperation *invocationOperation = [self invocationOperationWithData:data];
if (userInput.length == 0) {
invocationOperation.invocation.selector = @selector(myTaskMethod2:);
}
return invocationOperation;
}
- (void)myTaskMethod2:(id)data {
NSLog(@"Start executing %@ with data: %@, mainThread: %@, currentThread: %@", NSStringFromSelector(_cmd), data, [NSThread mainThread], [NSThread currentThread]);
sleep(3);
NSLog(@"Finish executing %@", NSStringFromSelector(_cmd));
}
創(chuàng)建 NSBlockOperation 對(duì)象
NSBlockOperation 是 NSOperation 類的另外一個(gè)系統(tǒng)預(yù)定義的子類湾趾,我們可以用它來封裝一個(gè)或多個(gè) block 。我們知道 GCD 主要就是用來進(jìn)行 block 調(diào)度的派草,那為什么我們還需要 NSBlockOperation 類呢搀缠?一般來說,有以下兩個(gè)場(chǎng)景我們會(huì)優(yōu)先使用 NSBlockOperation 類:
當(dāng)我們?cè)趹?yīng)用中已經(jīng)使用了 Operation Queues 且不想創(chuàng)建 Dispatch Queues 時(shí)近迁,NSBlockOperation 類可以為我們的應(yīng)用提供一個(gè)面向?qū)ο蟮姆庋b艺普;
我們需要用到 Dispatch Queues 不具備的功能時(shí),比如需要設(shè)置 operation 之間的依賴關(guān)系、使用 KVO 觀察 operation 的狀態(tài)變化等歧譬。
下面的示例代碼展示了創(chuàng)建一個(gè) NSBlockOperation 對(duì)象的基本方法:
@implementation OQCreateBlockOperation
- (NSBlockOperation *)blockOperation {
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Start executing block1, mainThread: %@, currentThread: %@", [NSThread mainThread], [NSThread currentThread]);
sleep(3);
NSLog(@"Finish executing block1");
}];
[blockOperation addExecutionBlock:^{
NSLog(@"Start executing block2, mainThread: %@, currentThread: %@", [NSThread mainThread], [NSThread currentThread]);
sleep(3);
NSLog(@"Finish executing block2");
}];
[blockOperation addExecutionBlock:^{
NSLog(@"Start executing block3, mainThread: %@, currentThread: %@", [NSThread mainThread], [NSThread currentThread]);
sleep(3);
NSLog(@"Finish executing block3");
}];
return blockOperation;
}
@end
自定義 Operation 對(duì)象
當(dāng)系統(tǒng)預(yù)定義的兩個(gè)子類 NSInvocationOperation 和 NSBlockOperation 不能很好的滿足我們的需求時(shí)岸浑,我們可以自定義自己的 NSOperation 子類,添加我們想要的功能瑰步。目前矢洲,我們可以自定義非并發(fā)和并發(fā)兩種不同類型的 NSOperation 子類,而自定義一個(gè)前者要比后者簡(jiǎn)單得多缩焦。
對(duì)于一個(gè)非并發(fā)的 operation 读虏,我們需要做的就只是執(zhí)行 main 方法中的任務(wù)以及能夠正常響應(yīng)取消事件就可以了,其它的復(fù)雜工作比如依賴配置袁滥、KVO 通知等 NSOperation 類都已經(jīng)幫我們處理好了盖桥。而對(duì)于一個(gè)并發(fā)的 operation ,我們還需要重寫 NSOperation 類中的一些現(xiàn)有方法题翻。接下來揩徊,我們將會(huì)介紹如何自定義這兩種不同類型的 NSOperation 子類。
執(zhí)行主任務(wù)
從最低限度上來說嵌赠,每一個(gè) operation 都應(yīng)該至少實(shí)現(xiàn)以下兩個(gè)方法:
一個(gè)自定義的初始化方法塑荒;
main 方法。
我們需要用一個(gè)自定義的初始化方法來將創(chuàng)建的 operation 置于一個(gè)已知的狀態(tài)姜挺,并且重寫 main 方法來執(zhí)行我們的任務(wù)袜炕。當(dāng)然,我們也可以實(shí)現(xiàn)一些其他的額外方法初家,比如實(shí)現(xiàn) NSCoding 協(xié)議來允許我們歸檔和解檔 operation 等。下面的示例代碼展示了如何自定義一個(gè)簡(jiǎn)單的 operation :
@interface OQNonConcurrentOperation ()
@property (strong, nonatomic) id data;
@end
@implementation OQNonConcurrentOperation
- (id)initWithData:(id)data {
self = [super init];
if (self) {
self.data = data;
}
return self;
}
///? 不支持取消操作
- (void)main {
@try {
NSLog(@"Start executing %@ with data: %@, mainThread: %@, currentThread: %@", NSStringFromSelector(_cmd), self.data, [NSThread mainThread], [NSThread currentThread]);
sleep(3);
NSLog(@"Finish executing %@", NSStringFromSelector(_cmd));
}
@catch(NSException *exception) {
NSLog(@"Exception: %@", exception);
}
}
@end
響應(yīng)取消事件
當(dāng)一個(gè) operation 開始執(zhí)行后乌助,它會(huì)一直執(zhí)行它的任務(wù)直到完成或被取消為止溜在。我們可以在任意時(shí)間點(diǎn)取消一個(gè) operation ,甚至是在它還未開始執(zhí)行之前他托。為了讓我們自定義的 operation 能夠支持取消事件掖肋,我們需要在代碼中定期地檢查 isCancelled 方法的返回值,一旦檢查到這個(gè)方法返回 YES 赏参,我們就需要立即停止執(zhí)行接下來的任務(wù)志笼。根據(jù)蘋果官方的說法,isCancelled 方法本身是足夠輕量的把篓,所以就算是頻繁地調(diào)用它也不會(huì)給系統(tǒng)帶來太大的負(fù)擔(dān)纫溃。
The isCancelled method itself is very lightweight and can be called frequently without any significant performance penalty.
通常來說,當(dāng)我們自定義一個(gè) operation 類時(shí)韧掩,我們需要考慮在以下幾個(gè)關(guān)鍵點(diǎn)檢查 isCancelled 方法的返回值:
在真正開始執(zhí)行任務(wù)之前紊浩;
至少在每次循環(huán)中檢查一次,而如果一次循環(huán)的時(shí)間本身就比較長(zhǎng)的話,則需要檢查得更加頻繁坊谁;
在任何相對(duì)來說比較容易中止 operation 的地方费彼。
看到這里,我想你應(yīng)該可以意識(shí)到一點(diǎn)口芍,那就是盡管 operation 是支持取消操作的箍铲,但卻并不是立即取消的,而是在你調(diào)用了 operation 的 cancel 方法之后的下一個(gè) isCancelled 的檢查點(diǎn)取消的鬓椭。
///? 支持取消操作
- (void)main {
@try {
if (self.isCancelled) return;
NSLog(@"Start executing %@ with data: %@, mainThread: %@, currentThread: %@", NSStringFromSelector(_cmd), self.data, [NSThread mainThread], [NSThread currentThread]);
for (NSUInteger i = 0; i < 3; i++) {
if (self.isCancelled) return;
sleep(1);
NSLog(@"Loop %@", @(i + 1));
}
NSLog(@"Finish executing %@", NSStringFromSelector(_cmd));
}
@catch(NSException *exception) {
NSLog(@"Exception: %@", exception);
}
}
配置并發(fā)執(zhí)行的 Operation
在默認(rèn)情況下颠猴,operation 是同步執(zhí)行的,也就是說在調(diào)用它的 start 方法的線程中執(zhí)行它們的任務(wù)膘融。而在 operation 和 operation queue 結(jié)合使用時(shí)芙粱,operation queue 可以為非并發(fā)的 operation 提供線程,因此氧映,大部分的 operation 仍然可以異步執(zhí)行春畔。但是,如果你想要手動(dòng)地執(zhí)行一個(gè) operation 岛都,又想這個(gè) operation 能夠異步執(zhí)行的話律姨,你需要做一些額外的配置來讓你的 operation 支持并發(fā)執(zhí)行。下面列舉了一些你可能需要重寫的方法:
start :必須的臼疫,所有并發(fā)執(zhí)行的 operation 都必須要重寫這個(gè)方法择份,替換掉 NSOperation 類中的默認(rèn)實(shí)現(xiàn)。start 方法是一個(gè) operation 的起點(diǎn)烫堤,我們可以在這里配置任務(wù)執(zhí)行的線程或者一些其它的執(zhí)行環(huán)境荣赶。另外,需要特別注意的是鸽斟,在我們重寫的 start 方法中一定不要調(diào)用父類的實(shí)現(xiàn)拔创;
main :可選的,通常這個(gè)方法就是專門用來實(shí)現(xiàn)與該 operation 相關(guān)聯(lián)的任務(wù)的富蓄。盡管我們可以直接在 start 方法中執(zhí)行我們的任務(wù)剩燥,但是用 main 方法來實(shí)現(xiàn)我們的任務(wù)可以使設(shè)置代碼和任務(wù)代碼得到分離,從而使 operation 的結(jié)構(gòu)更清晰立倍;
isExecuting 和 isFinished :必須的灭红,并發(fā)執(zhí)行的 operation 需要負(fù)責(zé)配置它們的執(zhí)行環(huán)境,并且向外界客戶報(bào)告執(zhí)行環(huán)境的狀態(tài)口注。因此变擒,一個(gè)并發(fā)執(zhí)行的 operation 必須要維護(hù)一些狀態(tài)信息,用來記錄它的任務(wù)是否正在執(zhí)行疆导,是否已經(jīng)完成執(zhí)行等赁项。此外葛躏,當(dāng)這兩個(gè)方法所代表的值發(fā)生變化時(shí),我們需要生成相應(yīng)的 KVO 通知悠菜,以便外界能夠觀察到這些狀態(tài)的變化舰攒;
isConcurrent :必須的,這個(gè)方法的返回值用來標(biāo)識(shí)一個(gè) operation 是否是并發(fā)的 operation 悔醋,我們需要重寫這個(gè)方法并返回 YES 摩窃。
下面我們將分三部分內(nèi)容來介紹一下定義一個(gè)并發(fā)執(zhí)行的 operation 所需的基本代碼,主體部分的代碼如下所示:
@implementation OQConcurrentOperation
@synthesize executing = _executing;
@synthesize finished? = _finished;
- (id)init {
self = [super init];
if (self) {
_executing = NO;
_finished? = NO;
}
return self;
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return _executing;
}
- (BOOL)isFinished {
return _finished;
}
@end
這一部分的代碼看上去比較簡(jiǎn)單芬骄,但是卻需要我們用心地去理解它猾愿。首先,我們用 @synthesize 關(guān)鍵字手動(dòng)合成了兩個(gè)實(shí)例變量 _executing 和 _finished 账阻,然后分別在重寫的 isExecuting 和 isFinished 方法中返回了這兩個(gè)實(shí)例變量蒂秘。另外,我們通過查看 NSOperation 類的頭文件可以發(fā)現(xiàn)淘太,executing 和 finished 屬性都被聲明成了只讀的 readonly 姻僧。所以我們?cè)?NSOperation 子類中就沒有辦法直接通過 setter 方法來自動(dòng)觸發(fā) KVO 通知,這也是為什么我們需要在接下來的代碼中手動(dòng)觸發(fā) KVO 通知的原因蒲牧。
接下來是 start 方法的代碼撇贺,在這個(gè)方法中,我們最需要關(guān)注的部分就是為 main 方法分離了一個(gè)新的線程冰抢,這是 operation 能夠并發(fā)執(zhí)行的關(guān)鍵所在松嘶。此外,在真正開始執(zhí)行任務(wù)前挎扰,我們通過檢查 isCancelled 方法的返回值來判斷 operation 是否已經(jīng)被 cancel 翠订,如果是就直接返回了。
- (void)start {
if (self.isCancelled) {
[self willChangeValueForKey:@"isFinished"];
_finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
_executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
最后遵倦,是真正執(zhí)行任務(wù)的 main 方法蕴轨,值得注意的是在任務(wù)執(zhí)行完畢后,我們需要手動(dòng)觸動(dòng) isExecuting 和 isFinished 的 KVO 通知骇吭。
- (void)main {
@try {
NSLog(@"Start executing %@, mainThread: %@, currentThread: %@", NSStringFromSelector(_cmd), [NSThread mainThread], [NSThread currentThread]);
sleep(3);
[self willChangeValueForKey:@"isExecuting"];
_executing = NO;
[self didChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_finished? = YES;
[self didChangeValueForKey:@"isFinished"];
NSLog(@"Finish executing %@", NSStringFromSelector(_cmd));
}
@catch (NSException *exception) {
NSLog(@"Exception: %@", exception);
}
}
注意,有一個(gè)非常重要的點(diǎn)需要引起我們的注意歧寺,那就是即使一個(gè) operation 是被 cancel 掉了燥狰,我們?nèi)匀恍枰謩?dòng)觸發(fā) isFinished 的 KVO 通知。因?yàn)楫?dāng)一個(gè) operation 依賴其他 operation 時(shí)斜筐,它會(huì)觀察所有其他 operation 的 isFinished 的值的變化龙致,只有當(dāng)它依賴的所有 operation 的 isFinished 的值為 YES 時(shí),這個(gè) operation 才能夠開始執(zhí)行顷链。因此目代,如果一個(gè)我們自定義的 operation 被取消了但卻沒有手動(dòng)觸發(fā) isFinished 的 KVO 通知的話,那么所有依賴它的 operation 都不會(huì)執(zhí)行。
維護(hù) KVO 通知
NSOperation 類的以下 key paths 支持 KVO 通知榛了,我們可以通過觀察這些 key paths 非常方便地監(jiān)聽到一個(gè) operation 內(nèi)部狀態(tài)的變化:
isCancelled
isConcurrent
isExecuting
isFinished
isReady
dependencies
queuePriority
completionBlock
與重寫 main 方法不同的是在讶,如果我們重寫了 start 方法或者對(duì) NSOperation 類做了大量定制的話,我們需要保證自定義的 operation 在這些 key paths 上仍然支持 KVO 通知霜大。比如构哺,當(dāng)我們重寫了 start 方法時(shí),我們需要特別關(guān)注的是 isExecuting 和 isFinished 這兩個(gè) key paths 战坤,因?yàn)檫@兩個(gè) key paths 最可能受重寫 start 方法的影響曙强。
定制 Operation 對(duì)象的執(zhí)行行為
我們可以在創(chuàng)建一個(gè) operation 后,添加到 operation queue 前途茫,對(duì) operation 的一些執(zhí)行行為進(jìn)行定制碟嘴。下面介紹的所有定制均適用于所有的 operation ,與是否是自定義的 NSOperation 子類或系統(tǒng)預(yù)定義的 NSOperation 子類無關(guān)囊卜。
配置依賴關(guān)系
通過配置依賴關(guān)系娜扇,我們可以讓不同的 operation 串行執(zhí)行,正如我們前面提到的边败,一個(gè) operation 只有在它依賴的所有 operation 都執(zhí)行完成后才能開始執(zhí)行袱衷。配置 operation 的依賴關(guān)系主要涉及到 NSOperation 類中的以下兩個(gè)方法:
1
2
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;
顧名思義,第一個(gè)方法用于添加依賴笑窜,第二個(gè)方法則用于移除依賴致燥。需要特別注意的是,用 addDependency: 方法添加的依賴關(guān)系是單向的排截,比如 [A addDependency:B]; 嫌蚤,表示 A 依賴 B,B 并不依賴 A 断傲。
另外脱吱,這里的依賴關(guān)系并不局限于相同 operation queue 中的 operation 之間。其實(shí)认罩,從上面兩個(gè)配置依賴關(guān)系的方法是存在于 NSOperation 類中的箱蝠,我們也可以看出來,operation 的依賴關(guān)系是它自己管理的垦垂,與它被添加到哪個(gè) operation queue 無關(guān)宦搬。因此,我們完全可以給一些 operation 配置好依賴關(guān)系劫拗,然后將它們添加到不同的 operation queue 中间校。但是,有一點(diǎn)是需要我們特別注意的页慷,就是不要在 operation 之間添加循環(huán)依賴憔足,因?yàn)檫@樣會(huì)導(dǎo)致這些 operation 都不會(huì)被執(zhí)行胁附。
注意,我們應(yīng)該在手動(dòng)執(zhí)行一個(gè) operation 或?qū)⑺砑拥?operation queue 前配置好依賴關(guān)系滓彰,因?yàn)樵谥筇砑拥囊蕾囮P(guān)系可能會(huì)失效控妻。
修改 Operation 在隊(duì)列中的優(yōu)先級(jí)
對(duì)于被添加到 operation queue 中的 operation 來說,決定它們執(zhí)行順序的第一要素是它們的 isReady 狀態(tài)找蜜,其次是它們?cè)陉?duì)列中的優(yōu)先級(jí)饼暑。operation 的 isReady 狀態(tài)取決于它的依賴關(guān)系,而在隊(duì)列中的優(yōu)先級(jí)則是 operation 本身的屬性洗做。默認(rèn)情況下弓叛,所有新創(chuàng)建的 operation 的隊(duì)列優(yōu)先級(jí)都是 normal 的,但是我們可以根據(jù)需要通過 setQueuePriority: 方法來提高或降低 operation 的隊(duì)列優(yōu)先級(jí)诚纸。
需要注意的是撰筷,隊(duì)列優(yōu)先級(jí)只應(yīng)用于相同 operation queue 中的 operation 之間,不同 operation queue 中的 operation 不受此影響畦徘。另外毕籽,我們也需要清楚 operation 的隊(duì)列優(yōu)先級(jí)和依賴關(guān)系之間的區(qū)別。operation 的隊(duì)列優(yōu)先級(jí)只決定當(dāng)前所有 isReady 狀態(tài)為 YES 的 operation 的執(zhí)行順序井辆。比如关筒,在一個(gè) operation queue 中,有一個(gè)高優(yōu)先級(jí)和一個(gè)低優(yōu)先級(jí)的 operation 杯缺,并且它們的 isReady 狀態(tài)都為 YES 蒸播,那么高優(yōu)先級(jí)的 operation 將會(huì)優(yōu)先執(zhí)行。而如果這個(gè)高優(yōu)先級(jí)的 operation 的 isReady 狀態(tài)為 NO 萍肆,而低優(yōu)先級(jí)的 operation 的 isReady 狀態(tài)為 YES 的話袍榆,那么這個(gè)低優(yōu)先級(jí)的 operation 反而會(huì)優(yōu)先執(zhí)行。
修改 Operation 執(zhí)行任務(wù)線程的優(yōu)先級(jí)
從 iOS 4.0 開始塘揣,我們可以修改 operation 的執(zhí)行任務(wù)線程的優(yōu)先級(jí)包雀。雖然 iOS 系統(tǒng)中的線程策略是由 kernel 內(nèi)核管理的,但是一般來說亲铡,高優(yōu)先級(jí)的線程相對(duì)于低優(yōu)先級(jí)的線程來說能夠得到更多的運(yùn)行機(jī)會(huì)才写。我們可以給 operation 的線程優(yōu)先級(jí)指定一個(gè)從 0.0 到 1.0 的浮點(diǎn)數(shù)值,0.0 表示最低的優(yōu)先級(jí)奖蔓,1.0 表示最高的優(yōu)先級(jí)琅摩,默認(rèn)值為 0.5 。
注意锭硼,我們只能夠在執(zhí)行一個(gè) operation 或?qū)⑵涮砑拥?operation queue 前,通過 operation 的 setThreadPriority: 方法來修改它的線程優(yōu)先級(jí)蜕劝。當(dāng) operation 開始執(zhí)行時(shí)檀头,NSOperation 類中默認(rèn)的 start 方法會(huì)使用我們指定的值來修改當(dāng)前線程的優(yōu)先級(jí)轰异。另外,我們指定的這個(gè)線程優(yōu)先級(jí)只會(huì)影響 main 方法執(zhí)行時(shí)所在線程的優(yōu)先級(jí)暑始。所有其它的代碼搭独,包括 operation 的 completion block 所在的線程會(huì)一直以默認(rèn)的線程優(yōu)先級(jí)執(zhí)行。因此廊镜,當(dāng)我們自定義一個(gè)并發(fā)的 operation 類時(shí)牙肝,我們也需要在 start 方法中根據(jù)指定的值自行修改線程的優(yōu)先級(jí)。
設(shè)置 Completion Block
從 iOS 4.0 開始嗤朴,一個(gè) operation 可以在它的主任務(wù)執(zhí)行完成時(shí)回調(diào)一個(gè) completion block 配椭。我們可以用 completion block 來執(zhí)行一些主任務(wù)之外的工作,比如雹姊,我們可以用它來通知一些客戶 operation 已經(jīng)執(zhí)行完畢股缸,而并發(fā)的 operation 也可以用這個(gè) block 來生成最終的 KVO 通知。如果需要設(shè)置一個(gè) operation 的 completion block 吱雏,直接調(diào)用 NSOperation 類的 setCompletionBlock: 方法即可敦姻。
注意,當(dāng)一個(gè) operation 被取消時(shí)歧杏,它的 completion block 仍然會(huì)執(zhí)行镰惦,所以我們需要在真正執(zhí)行代碼前檢查一下 isCancelled 方法的返回值。另外犬绒,我們也沒有辦法保證 completion block 被回調(diào)時(shí)一定是在主線程旺入,理論上它應(yīng)該是與觸發(fā) isFinished 的 KVO 通知所在的線程一致的,所以如果有必要的話我們可以在 completion block 中使用 GCD 來保證從主線程更新 UI 懂更。
執(zhí)行 Operation 對(duì)象
最終眨业,我們需要執(zhí)行 operation 來調(diào)度與其關(guān)聯(lián)的任務(wù)。目前沮协,主要有兩種方式來執(zhí)行一個(gè) operation :
將 operation 添加到一個(gè) operation queue 中龄捡,讓 operation queue 來幫我們自動(dòng)執(zhí)行;
直接調(diào)用 start 方法手動(dòng)執(zhí)行 operation 慷暂。
添加 Operation 到 Operation Queue 中
就目前來說聘殖,將 operation 添加到 operation queue 中是最簡(jiǎn)單的執(zhí)行 operation 的方式。另外行瑞,這里的 operation queue 指的就是 NSOperationQueue 類的一個(gè)具體實(shí)例奸腺。就技術(shù)上而言,我們可以在應(yīng)用中創(chuàng)建任意數(shù)量的 operation queue 血久,但是 operation queue 的數(shù)量越多并不意味著我們就能同時(shí)執(zhí)行越多的 operation 突照。因?yàn)橥瑫r(shí)并發(fā)的 operation 數(shù)量是由系統(tǒng)決定的,系統(tǒng)會(huì)根據(jù)當(dāng)前可用的核心數(shù)以及負(fù)載情況動(dòng)態(tài)地調(diào)整最大的并發(fā) operation 數(shù)量氧吐。創(chuàng)建一個(gè) operation queue 非常簡(jiǎn)單讹蘑,跟創(chuàng)建其他普通對(duì)象沒有任何區(qū)別:
1
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
創(chuàng)建好 operation queue 后末盔,我們可以使用下面三個(gè)方法添加 operation 到 operation queue 中:
addOperation: ,添加一個(gè) operation 到 operation queue 中座慰;
addOperations:waitUntilFinished: 陨舱,添加一組 operation 到 operation queue 中;
addOperationWithBlock: 版仔,直接添加一個(gè) block 到 operation queue 中游盲,而不用創(chuàng)建一個(gè) NSBlockOperation 對(duì)象。
在大多數(shù)情況下蛮粮,一個(gè) operation 被添加到 operation queue 后不久就會(huì)執(zhí)行益缎,但是也有很多原因會(huì)使 operation queue 延遲執(zhí)行入隊(duì)的 operation 。比如蝉揍,我們前面提到了的链峭,如果一個(gè) operation 所依賴的其他 operation 還沒有執(zhí)行完成時(shí),這個(gè) operation 就不能開始執(zhí)行又沾;再比如說 operation queue 被暫停執(zhí)行或者已經(jīng)達(dá)到了它最大可并發(fā)的 operation 數(shù)弊仪。下面的示例代碼展示了這三種方法的基本用法:
@implementation OQUseOperationQueue
- (void)executeOperationUsingOperationQueue {
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(taskMethod) object:nil];
[operationQueue addOperation:invocationOperation];
NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Start executing blockOperation1, mainThread: %@, currentThread: %@", [NSThread mainThread], [NSThread currentThread]);
sleep(3);
NSLog(@"Finish executing blockOperation1");
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Start executing blockOperation2, mainThread: %@, currentThread: %@", [NSThread mainThread], [NSThread currentThread]);
sleep(3);
NSLog(@"Finish executing blockOperation2");
}];
[operationQueue addOperations:@[ blockOperation1, blockOperation2 ] waitUntilFinished:NO];
[operationQueue addOperationWithBlock:^{
NSLog(@"Start executing block, mainThread: %@, currentThread: %@", [NSThread mainThread], [NSThread currentThread]);
sleep(3);
NSLog(@"Finish executing block");
}];
[operationQueue waitUntilAllOperationsAreFinished];
}
- (void)taskMethod {
NSLog(@"Start executing %@, mainThread: %@, currentThread: %@", NSStringFromSelector(_cmd), [NSThread mainThread], [NSThread currentThread]);
sleep(3);
NSLog(@"Finish executing %@", NSStringFromSelector(_cmd));
}
@end
注意护赊,在將一個(gè) operation 添加到 operation queue 后就不要再修改這個(gè) operation 了萍悴。因?yàn)?operation 被添加到 operation queue 后隨時(shí)可能會(huì)執(zhí)行痊末,這個(gè)是由系統(tǒng)決定的题画,所以再修改它的依賴關(guān)系或者所包含的數(shù)據(jù)就很有可能會(huì)造成未知的影響六孵。
盡管 NSOperationQueue 類是被設(shè)計(jì)成用來并發(fā)執(zhí)行 operation 的嵌削,但是我們也可以強(qiáng)制一個(gè) operation queue 一次只執(zhí)行一個(gè) operation 吓歇。我們可以通過 setMaxConcurrentoperationCount: 方法來設(shè)置一個(gè) operation queue 最大可并發(fā)的 operation 數(shù)一铅,因此將這個(gè)值設(shè)置成 1 就可以實(shí)現(xiàn)讓 operation queue 一次只執(zhí)行一個(gè) operation 的目的表窘。但是需要注意的是典予,雖然這樣可以讓 operation queue 一次只執(zhí)行一個(gè) operation ,但是 operation 的執(zhí)行順序還是一樣會(huì)受其他因素影響的乐严,比如 operation 的 isReady 狀態(tài)瘤袖、operation 的隊(duì)列優(yōu)先級(jí)等。因此昂验,一個(gè)串行的 operation queue 與一個(gè)串行的 dispatch queue 還是有本質(zhì)區(qū)別的捂敌,因?yàn)?dispatch queue 的執(zhí)行順序一直是 FIFO 的。如果 operation 的執(zhí)行順序?qū)ξ覀儊碚f非常重要既琴,那么我們就應(yīng)該在將 operation 添加到 operation queue 之前就建立好它的依賴關(guān)系占婉。
手動(dòng)執(zhí)行 Operation
盡管使用 operation queue 是執(zhí)行一個(gè) operation 最方便的方式,但是我們也可以不用 operation queue 而選擇手動(dòng)地執(zhí)行一個(gè) operation 甫恩。從原理上來說逆济,手動(dòng)執(zhí)行一個(gè) operation 也是非常簡(jiǎn)單的,只需要調(diào)用它的 start 方法就可以了。但是從嚴(yán)格意義上來說奖慌,在調(diào)用 start 方法真正開始執(zhí)行一個(gè) operation 前霎终,我們應(yīng)該要做一些防范性的判斷,比如檢查 operation 的 isReady 狀態(tài)是否為 YES 升薯,這個(gè)取決于它所依賴的 operation 是否已經(jīng)執(zhí)行完成;又比如檢查 operation 的 isCancelled 狀態(tài)是否為 YES 击困,如果是涎劈,那么我們就根本不需要再花費(fèi)不必要的開銷去啟動(dòng)它。
另外阅茶,我們應(yīng)該一直通過 start 方法去手動(dòng)執(zhí)行一個(gè) operation 蛛枚,而不是 main 或其他的什么方法。因?yàn)槟J(rèn)的 start 方法會(huì)在真正開始執(zhí)行任務(wù)前為我們做一些安全性的檢查脸哀,比如檢查 operation 是否已取消等蹦浦。另外,正如我們前面說的撞蜂,在默認(rèn)的 start 方法中會(huì)生成一些必要的 KVO 通知盲镶,比如 isExcuting 和 isFinished ,而這些 KVO 通知正是 operation 能夠正確處理好依賴關(guān)系的關(guān)鍵所在蝌诡。
更進(jìn)一步說溉贿,如果我們需要實(shí)現(xiàn)的是一個(gè)并發(fā)的 operation ,我們也應(yīng)該在啟動(dòng) operation 前檢查一下它的 isConcurrent 狀態(tài)浦旱。如果它的 isConcurrent 狀態(tài)為 NO 宇色,那么我們就需要考慮一下是否可以在當(dāng)前線程同步執(zhí)行這個(gè) operation ,或者是先為這個(gè) operation 創(chuàng)建一個(gè)單獨(dú)的線程颁湖,以供它異步執(zhí)行宣蠕。
當(dāng)然,如果你已經(jīng)能夠確定一個(gè) operation 的可執(zhí)行狀態(tài)甥捺,那么你大可不必做這些略顯啰嗦的防范性檢查抢蚀,直接調(diào)用 start 方法執(zhí)行這個(gè) operation 即可。下面的示例代碼展示了手動(dòng)執(zhí)行一個(gè) operation 的基本流程:
@implementation OQManualExecuteOperation
- (BOOL)manualPerformOperation:(NSOperation *)operation {
BOOL ranIt = NO;
if (operation.isCancelled) {
ranIt = YES;
} else if (operation.isReady) {
if (!operation.isConcurrent) {
[operation start];
} else {
[NSThread detachNewThreadSelector:@selector(start) toTarget:operation withObject:nil];
}
ranIt = YES;
}
return ranIt;
}
@end
取消 Operation
從原則上來說涎永,一旦一個(gè) operation 被添加到 operation queue 后思币,這個(gè) operation 的所有權(quán)就屬于這個(gè) operation queue 了,并且不能夠被移除羡微。唯一從 operation queue 中出隊(duì)一個(gè) operation 的方式就是調(diào)用它的 cancel 方法取消這個(gè) operation 谷饿,或者直接調(diào)用 operation queue 的 cancelAllOperations 方法取消這個(gè) operation queue 中所有的 operation 。另外妈倔,我們前面也提到了博投,當(dāng)一個(gè) operation 被取消后,這個(gè) operation 的 isFinished 狀態(tài)也會(huì)變成 YES 盯蝴,這樣處理的好處就是所有依賴它的 operation 能夠接收到這個(gè) KVO 通知毅哗,從而能夠清除這個(gè)依賴關(guān)系正常執(zhí)行听怕。
等待 Operation 執(zhí)行完成
一般來說,為了讓我們的應(yīng)用擁有最佳的性能虑绵,我們應(yīng)該盡可能地異步執(zhí)行所有的 operation 尿瞭,從而讓我們的應(yīng)用在執(zhí)行這些異步 operation 的同時(shí)還能夠快速地響應(yīng)用戶事件。當(dāng)然翅睛,我們也可以調(diào)用 NSOperation 類的 waitUntilFinished 方法來阻塞當(dāng)前線程声搁,直到這個(gè) operation 執(zhí)行完成。雖然這種方式可以讓我們非常方便地處理 operation 的執(zhí)行結(jié)果捕发,但是卻給我們的應(yīng)用引入了更多的串行疏旨,限制了應(yīng)用的并發(fā)性,從而降低了我們應(yīng)用的響應(yīng)性扎酷。
注意檐涝,我們應(yīng)該要堅(jiān)決避免在主線程中去同步等待一個(gè) operation 的執(zhí)行結(jié)果,阻塞的方式只應(yīng)該用在輔助線程或其他 operation 中法挨。因?yàn)樽枞骶€程會(huì)大大地降低我們應(yīng)用的響應(yīng)性谁榜,帶來非常差的用戶體驗(yàn)。
除了等待一個(gè)單獨(dú)的 operation 執(zhí)行完成外坷剧,我們也可以通過調(diào)用 NSOperationQueue 的 waitUntilAlloperationsAreFinished 方法來等待 operation queue 中的所有 operation 執(zhí)行完成惰爬。有一點(diǎn)需要特別注意的是,當(dāng)我們?cè)诘却粋€(gè) operation queue 中的所有 operation 執(zhí)行完成時(shí)惫企,其他的線程仍然可以向這個(gè) operation queue 中添加 operation ,從而延長(zhǎng)我們的等待時(shí)間狞尔。
暫停和恢復(fù) Operation Queue
如果我們想要暫停和恢復(fù)執(zhí)行 operation queue 中的 operation 丛版,可以通過調(diào)用 operation queue 的 setSuspended: 方法來實(shí)現(xiàn)這個(gè)目的。不過需要注意的是偏序,暫停執(zhí)行 operation queue 并不能使正在執(zhí)行的 operation 暫停執(zhí)行页畦,而只是簡(jiǎn)單地暫停調(diào)度新的 operation 。另外研儒,我們并不能單獨(dú)地暫停執(zhí)行一個(gè) operation 豫缨,除非直接 cancel 掉。
總結(jié)
看到這里端朵,我想你對(duì) iOS 的并發(fā)編程模型已經(jīng)有了一定的了解好芭。正如文中所說的,我們應(yīng)該盡可能地直接使用隊(duì)列而不是線程冲呢,讓系統(tǒng)去與線程打交道舍败,而我們只需定義好要調(diào)度的任務(wù)就可以了。一般情況下,我們也完全不需要去自定義一個(gè)并發(fā)的 operation 邻薯,因?yàn)樵谂c operation queue 結(jié)合使用時(shí)裙戏,operation queue 會(huì)自動(dòng)為非并發(fā)的 operation 創(chuàng)建一個(gè)線程。Operation Queues 是對(duì) GCD 面向?qū)ο蟮姆庋b厕诡,它可以高度定制化累榜,對(duì)依賴關(guān)系、隊(duì)列優(yōu)先級(jí)和線程優(yōu)先級(jí)等提供了很好的支持灵嫌,是我們實(shí)現(xiàn)復(fù)雜任務(wù)調(diào)度時(shí)的不二之選信柿。