iOS Concurrency Programming Guide
iOS 和 Mac OS 傳統(tǒng)的并發(fā)編程模型是線(xiàn)程芳杏,不過(guò)線(xiàn)程模型伸縮性不強(qiáng),而且編寫(xiě)正確的線(xiàn)程代碼也不容易纪隙。Mac OS 和 iOS 采取 asynchronous design approach 來(lái)解決并發(fā)的問(wèn)題。
引入的異步技術(shù)有兩個(gè):
Grand Central Dispatch:系統(tǒng)管理線(xiàn)程扛或,你不需要編寫(xiě)線(xiàn)程代碼绵咱。只需定義想要執(zhí)行的任務(wù),然后添加到適當(dāng)?shù)膁ispatch queue熙兔。Grand Central Dispatch會(huì)負(fù)責(zé)創(chuàng)建線(xiàn)程和調(diào)度你的任務(wù)悲伶。系統(tǒng)直接提供線(xiàn)程管理,比應(yīng)用實(shí)現(xiàn)更加高效住涉。
Operation Queue:Objective-C對(duì)象麸锉,類(lèi)似于dispatch queue。你定義想要執(zhí)行的任務(wù)舆声,并添加任務(wù)到operation queue花沉,后者負(fù)責(zé)調(diào)度和執(zhí)行這些任務(wù)柳爽。和Grand Central Dispatch一樣,Operation Queue也管理了線(xiàn)程碱屁,更加高效磷脯。
Dispatch Queue
基于C的執(zhí)行自定義任務(wù)機(jī)制。dispatch queue按先進(jìn)先出的順序娩脾,串行或并發(fā)地執(zhí)行任務(wù)赵誓。serial dispaptch queue一次只能執(zhí)行一個(gè)任務(wù),直接當(dāng)前任務(wù)完成才開(kāi)始出列并啟動(dòng)下一個(gè)任務(wù)柿赊。而concurrent dispatch queue則盡可能多地啟動(dòng)任務(wù)并發(fā)執(zhí)行架曹。
優(yōu)點(diǎn):
直觀(guān)而簡(jiǎn)單的編程接口
提供自動(dòng)和整體的線(xiàn)程池管理
提供匯編級(jí)調(diào)優(yōu)的速度
更加高效地使用內(nèi)存
不會(huì)trap內(nèi)核under load
異步分派任務(wù)到dispatch queue不會(huì)導(dǎo)致queue死鎖
伸縮性強(qiáng)
serial dispatch queue比鎖和其它同步原語(yǔ)更加高效
Dispatch Sources
Dispatch Sources 是基于C的系統(tǒng)事件異步處理機(jī)制。一個(gè)Dispatch Source封裝了一個(gè)特定類(lèi)型的系統(tǒng)事件闹瞧,當(dāng)事件發(fā)生時(shí)提交一個(gè)特定的block對(duì)象或函數(shù)到dispatch queue。你可以使用Dispatch Sources監(jiān)控以下類(lèi)型的系統(tǒng)事件:
定時(shí)器
信號(hào)處理器
描述符相關(guān)的事件
進(jìn)程相關(guān)的事件
Mach port事件
你觸發(fā)的自定義事件
Operation Queues
Operation Queues是Cocoa版本的并發(fā)dispatch queue展辞,由 NSOperationQueue 類(lèi)實(shí)現(xiàn)奥邮。dispatch queue總是按先進(jìn)先出的順序執(zhí)行任務(wù),而 Operation Queues 在確定任務(wù)執(zhí)行順序時(shí)罗珍,還會(huì)考慮其它因素洽腺。最主要的一個(gè)因素是指定任務(wù)是否依賴(lài)于另一個(gè)任務(wù)的完成。你在定義任務(wù)時(shí)配置依賴(lài)性覆旱,從而創(chuàng)建復(fù)雜的任務(wù)執(zhí)行順序圖
提交到Operation Queues的任務(wù)必須是 NSOperation 對(duì)象蘸朋,operation object封裝了你要執(zhí)行的工作,以及所需的所有數(shù)據(jù)扣唱。由于 NSOperation 是一個(gè)抽象基類(lèi)藕坯,通常你需要定義自定義子類(lèi)來(lái)執(zhí)行任務(wù)。不過(guò)Foundation framework自帶了一些具體子類(lèi)噪沙,你可以創(chuàng)建并執(zhí)行相關(guān)的任務(wù)炼彪。
Operation objects會(huì)產(chǎn)生key-value observing(KVO)通知,對(duì)于監(jiān)控任務(wù)的進(jìn)程非常有用正歼。雖然operation queue總是并發(fā)地執(zhí)行任務(wù)辐马,你可以使用依賴(lài),在需要時(shí)確保順序執(zhí)行
異步設(shè)計(jì)技術(shù)
通過(guò)確保主線(xiàn)程自由響應(yīng)用戶(hù)事件局义,并發(fā)可以很好地提高應(yīng)用的響應(yīng)性喜爷。通過(guò)將工作分配到多核,還能提高應(yīng)用處理的性能萄唇。但是并發(fā)也帶來(lái)一定的額外開(kāi)銷(xiāo)檩帐,并且使代碼更加復(fù)雜,更難編寫(xiě)和調(diào)試代碼另萤。
因此在應(yīng)用設(shè)計(jì)階段轿塔,就應(yīng)該考慮并發(fā),設(shè)計(jì)應(yīng)用需要執(zhí)行的任務(wù),及任務(wù)所需的數(shù)據(jù)結(jié)構(gòu)勾缭。
Operation Queues
基于Objective-C揍障,因此基于Cocoa的應(yīng)用通常會(huì)使用Operation Queues
Operation Objects
operation object 是 NSOperation 類(lèi)的實(shí)例,封裝了應(yīng)用需要執(zhí)行的任務(wù)俩由,和執(zhí)行任務(wù)所需的數(shù)據(jù)毒嫡。NSOperation 本身是抽象基類(lèi),我們必須實(shí)現(xiàn)子類(lèi)幻梯。Foundation framework提供了兩個(gè)具體子類(lèi)兜畸,你可以直接使用:
類(lèi) 描述
NSInvocationOperation 可以直接使用的類(lèi),基于應(yīng)用的一個(gè)對(duì)象和selector來(lái)創(chuàng)建operation object碘梢。如果你已經(jīng)有現(xiàn)有的方法來(lái)執(zhí)行需要的任務(wù)咬摇,就可以使用這個(gè)類(lèi)。
NSBlockOperation 可以直接使用的類(lèi)煞躬,用來(lái)并發(fā)地執(zhí)行一個(gè)或多個(gè)block對(duì)象肛鹏。operation object使用“組”的語(yǔ)義來(lái)執(zhí)行多個(gè)block對(duì)象,所有相關(guān)的block都執(zhí)行完成之后恩沛,operation object才算完成在扰。
NSOperation 基類(lèi),用來(lái)自定義子類(lèi)operation object雷客。繼承NSOperation可以完全控制operation object的實(shí)現(xiàn)芒珠,包括修改操作執(zhí)行和狀態(tài)報(bào)告的方式。
所有operation objects都支持以下關(guān)鍵特性:
支持建立基于圖的operation objects依賴(lài)搅裙≈遄浚可以阻止某個(gè)operation運(yùn)行,直到它依賴(lài)的所有operation都已經(jīng)完成部逮。
支持可選的completion block好爬,在operation的主任務(wù)完成后調(diào)用。
支持應(yīng)用使用KVO通知來(lái)監(jiān)控operation的執(zhí)行狀態(tài)甥啄。
支持operation優(yōu)先級(jí)存炮,從而影響相對(duì)的執(zhí)行順序
支持取消,允許你中止正在執(zhí)行的任務(wù)
并發(fā) VS 非并發(fā)Operations
通常我們通過(guò)將operation添加到operation queue中來(lái)執(zhí)行該操作蜈漓。但是我們也可以手動(dòng)調(diào)用start方法來(lái)執(zhí)行一個(gè)operation對(duì)象穆桂,這樣做不保證operation會(huì)并發(fā)執(zhí)行。NSOperation類(lèi)對(duì)象的 isConcurrent 方法告訴你這個(gè)operation相對(duì)于調(diào)用start方法的線(xiàn)程融虽,是同步還是異步執(zhí)行的享完。isConcurrent 方法默認(rèn)返回NO,表示operation與調(diào)用線(xiàn)程同步執(zhí)行有额。
如果你需要實(shí)現(xiàn)并發(fā)operation般又,也就是相對(duì)調(diào)用線(xiàn)程異步執(zhí)行的操作彼绷。你必須添加額外的代碼,來(lái)異步地啟動(dòng)操作茴迁。例如生成一個(gè)線(xiàn)程寄悯、調(diào)用異步系統(tǒng)函數(shù),以確保start方法啟動(dòng)任務(wù)堕义,并立即返回猜旬。
多數(shù)開(kāi)發(fā)者從來(lái)都不需要實(shí)現(xiàn)并發(fā)operation對(duì)象,我們只需要將operations添加到operation queue倦卖。當(dāng)你提交非并發(fā)operation到operation queue時(shí)洒擦,queue會(huì)創(chuàng)建線(xiàn)程來(lái)運(yùn)行你的操作,因此也能達(dá)到異步執(zhí)行的目的怕膛。只有你不希望使用operation queue來(lái)執(zhí)行operation時(shí)熟嫩,才需要定義并發(fā)operations。
創(chuàng)建一個(gè) NSInvocationOperation 對(duì)象
如果已經(jīng)現(xiàn)有一個(gè)方法褐捻,需要并發(fā)地執(zhí)行掸茅,就可以直接創(chuàng)建 NSInvocationOperation 對(duì)象,而不需要自己繼承 NSOperation舍扰。
@implementation MyCustomClass
- (NSOperation)taskWithData:(id)data {
NSInvocationOperation theOp = [[[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(myTaskMethod:) object:data] autorelease];
return theOp;
}
// This is the method that does the actual work of the task.
- (void)myTaskMethod:(id)data {
// Perform the task.
}
@end
創(chuàng)建一個(gè) NSBlockOperation 對(duì)象
NSBlockOperation 對(duì)象用于封裝一個(gè)或多個(gè)block對(duì)象,一般創(chuàng)建時(shí)會(huì)添加至少一個(gè)block希坚,然后再根據(jù)需要添加更多的block边苹。當(dāng) NSBlockOperation 對(duì)象執(zhí)行時(shí),會(huì)把所有block提交到默認(rèn)優(yōu)先級(jí)的并發(fā)dispatch queue裁僧。然后 NSBlockOperation 對(duì)象等待所有block完成執(zhí)行个束,最后標(biāo)記自己已完成。因此可以使用block operation來(lái)跟蹤一組執(zhí)行中的block聊疲,有點(diǎn)類(lèi)似于thread join等待多個(gè)線(xiàn)程的結(jié)果茬底。區(qū)別在于block operation本身也運(yùn)行在一個(gè)單獨(dú)的線(xiàn)程,應(yīng)用的其它線(xiàn)程在等待block operation完成時(shí)可以繼續(xù)工作获洲。
NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{
NSLog(@"Beginning operation.\n");
// Do some work.
}];
使用 addExecutionBlock: 可以添加更多block到這個(gè)block operation對(duì)象阱表。如果需要順序地執(zhí)行block,你必須直接提交到所需的dispatch queue贡珊。
自定義Operation對(duì)象
如果block operation和invocation operation對(duì)象不符合應(yīng)用的需求最爬,你可以直接繼承 NSOperation,并添加任何你想要的行為门岔。NSOperation 類(lèi)提供通用的子類(lèi)繼承點(diǎn)爱致,而且實(shí)現(xiàn)了許多重要的基礎(chǔ)設(shè)施來(lái)處理依賴(lài)和KVO通知。繼承所需的工作量主要取決于你要實(shí)現(xiàn)非并發(fā)還是并發(fā)的operation寒随。
定義非并發(fā)operation要簡(jiǎn)單許多糠悯,只需要執(zhí)行主任務(wù)帮坚,并正確地響應(yīng)取消事件;NSOperation 處理了其它所有事情。對(duì)于并發(fā)operation互艾,你必須替換某些現(xiàn)有的基礎(chǔ)設(shè)施代碼试和。
執(zhí)行主任務(wù)
每個(gè)operation對(duì)象至少需要實(shí)現(xiàn)以下方法:
自定義initialization方法:初始化,將operation 對(duì)象設(shè)置為已知狀態(tài)
自定義main方法:執(zhí)行你的任務(wù)
你也可以選擇性地實(shí)現(xiàn)以下方法:
main方法中需要調(diào)用的其它自定義方法
Accessor方法:設(shè)置和訪(fǎng)問(wèn)operation對(duì)象的數(shù)據(jù)
dealloc方法:清理operation對(duì)象分配的所有內(nèi)存
NSCoding 協(xié)議的方法:允許operation對(duì)象archive和unarchive
@interface MyNonConcurrentOperation : NSOperation {
id myData;
}
-(id)initWithData:(id)data;
@end
@implementation MyNonConcurrentOperation
(id)initWithData:(id)data {
if (self = [super init])
myData = [data retain];
return self;
}(void)dealloc {
[myData release];
[super dealloc];
}
-(void)main {
@try {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Do some work on myData and report the results.
[pool release];
}
@catch(...) {
// Do not rethrow exceptions.
}
}
@end
響應(yīng)取消事件
operation開(kāi)始執(zhí)行之后忘朝,會(huì)一直執(zhí)行任務(wù)直到完成灰署,或者顯式地取消操作。取消可能在任何時(shí)候發(fā)生局嘁,甚至在operation執(zhí)行之前溉箕。盡管 NSOperation 提供了一個(gè)方法,讓?xiě)?yīng)用取消一個(gè)操作悦昵,但是識(shí)別出取消事件則是你的事情肴茄。如果operation直接終止,可能無(wú)法回收所有已分配的內(nèi)存或資源但指。因此operation對(duì)象需要檢測(cè)取消事件寡痰,并優(yōu)雅地退出執(zhí)行。
operation 對(duì)象定期地調(diào)用 isCancelled 方法棋凳,如果返回YES(表示已取消)拦坠,則立即退出執(zhí)行。不管是自定義 NSOperation 子類(lèi)剩岳,還是使用系統(tǒng)提供的兩個(gè)具體子類(lèi)贞滨,都需要支持取消。isCancelled方法本身非常輕量拍棕,可以頻繁地調(diào)用而不產(chǎn)生大的性能損失晓铆。以下地方可能需要調(diào)用isCancelled:
在執(zhí)行任何實(shí)際的工作之前
在循環(huán)的每次迭代過(guò)程中,如果每個(gè)迭代相對(duì)較長(zhǎng)可能需要調(diào)用多次
代碼中相對(duì)比較容易中止操作的任何地方
- (void)main {
@try {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
BOOL isDone = NO;
while (![self isCancelled] && !isDone) {
// Do some work and set isDone to YES when finished
}
[pool release];
}
@catch(...) {
// Do not rethrow exceptions.
}
}
注意你的代碼還需要完成所有相關(guān)的資源清理工作
為并發(fā)執(zhí)行配置operations
Operation對(duì)象默認(rèn)按同步方式執(zhí)行绰播,也就是在調(diào)用start方法的那個(gè)線(xiàn)程中直接執(zhí)行骄噪。由于operation queue為非并發(fā)operation提供了線(xiàn)程支持,對(duì)應(yīng)用來(lái)說(shuō)蠢箩,多數(shù)operations仍然是異步執(zhí)行的链蕊。但是如果你希望手工執(zhí)行operations,而且仍然希望能夠異步執(zhí)行操作谬泌,你就必須采取適當(dāng)?shù)拇胧┦竟ㄟ^(guò)定義operation對(duì)象為并發(fā)操作來(lái)實(shí)現(xiàn)。
方法 描述
start (必須)所有并發(fā)操作都必須覆蓋這個(gè)方法呵萨,以自定義的實(shí)現(xiàn)替換默認(rèn)行為奏属。手動(dòng)執(zhí)行一個(gè)操作時(shí),你會(huì)調(diào)用start方法潮峦。因此你對(duì)這個(gè)方法的實(shí)現(xiàn)是操作的起點(diǎn)囱皿,設(shè)置一個(gè)線(xiàn)程或其它執(zhí)行環(huán)境勇婴,來(lái)執(zhí)行你的任務(wù)。你的實(shí)現(xiàn)在任何時(shí)候都絕對(duì)不能調(diào)用super嘱腥。
main (可選)這個(gè)方法通常用來(lái)實(shí)現(xiàn)operation對(duì)象相關(guān)聯(lián)的任務(wù)耕渴。盡管你可以在start方法中執(zhí)行任務(wù),使用main來(lái)實(shí)現(xiàn)任務(wù)可以讓你的代碼更加清晰地分離設(shè)置和任務(wù)代碼
isExecuting
isFinished (必須)并發(fā)操作負(fù)責(zé)設(shè)置自己的執(zhí)行環(huán)境齿兔,并向外部client報(bào)告執(zhí)行環(huán)境的狀態(tài)橱脸。因此并發(fā)操作必須維護(hù)某些狀態(tài)信息,以知道是否正在執(zhí)行任務(wù)分苇,是否已經(jīng)完成任務(wù)添诉。使用這兩個(gè)方法報(bào)告自己的狀態(tài)。
這兩個(gè)方法的實(shí)現(xiàn)必須能夠在其它多個(gè)線(xiàn)程中同時(shí)調(diào)用医寿。另外這些方法報(bào)告的狀態(tài)變化時(shí)栏赴,還需要為相應(yīng)的key path產(chǎn)生適當(dāng)?shù)腒VO通知。
isConcurrent (必須)標(biāo)識(shí)一個(gè)操作是否并發(fā)operation靖秩,覆蓋這個(gè)方法并返回YES
@interface MyOperation : NSOperation {
BOOL executing;
BOOL finished;
}
- (void)completeOperation;
@end
@implementation MyOperation
(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;
}(void)start {
// Always check for cancellation before launching the task.
if ([self isCancelled])
{
// Must move the operation to the finished state if it is canceled.
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
@try {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Do the main work of the operation here.
[self completeOperation];
[pool release];
}
@catch(...) {
// Do not rethrow exceptions.
}
}
- (void)completeOperation {
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
@end
即使操作被取消须眷,你也應(yīng)該通知KVO observers,你的操作已經(jīng)完成沟突。當(dāng)某個(gè)operation對(duì)象依賴(lài)于另一個(gè)operation對(duì)象的完成時(shí)花颗,它會(huì)監(jiān)測(cè)后者的isFinished key path。只有所有依賴(lài)的對(duì)象都報(bào)告已經(jīng)完成惠拭,第一個(gè)operation對(duì)象才會(huì)開(kāi)始運(yùn)行扩劝。如果你的operation對(duì)象沒(méi)有產(chǎn)生完成通知,就會(huì)阻止其它依賴(lài)于你的operation對(duì)象運(yùn)行求橄。
維護(hù)KVO依從
NSOperation類(lèi)的key-value observing(KVO)依從于以下key paths:
isCancelled
isConcurrent
isExecuting
isFinished
isReady
dependencies
queuePriority
completionBlock
如果你覆蓋start方法今野,或者對(duì)NSOperation對(duì)象的其它自定義運(yùn)行(覆蓋main除外)葡公,你必須確保自定義對(duì)象對(duì)這些key paths保留KVO依從罐农。覆蓋start方法時(shí),需要關(guān)注isExecuting和isFinished兩個(gè)key paths催什。
如果你希望實(shí)現(xiàn)依賴(lài)于其它東西(非operation對(duì)象)涵亏,你可以覆蓋isReady方法,并強(qiáng)制返回NO蒲凶,直到你等待的依賴(lài)得到滿(mǎn)足气筋。如果你需要保留默認(rèn)的依賴(lài)管理系統(tǒng),確保你調(diào)用了[super isReady]旋圆。當(dāng)你的operation對(duì)象的準(zhǔn)備就緒狀態(tài)發(fā)生改變時(shí)宠默,生成一個(gè)isReady的key path的KVO通知。
除非你覆蓋了 addDependency: 或 removeDependency: 方法灵巧,否則你不需要關(guān)注dependencies key path
雖然你也可以生成 NSOperation 的其它KVO通知搀矫,但通常你不需要這樣做抹沪。如果需要取消一個(gè)操作,你可以直接調(diào)用現(xiàn)有的cancel方法瓤球。類(lèi)似地融欧,你也很少需要修改queue優(yōu)先級(jí)信息。最后卦羡,除非你的operation對(duì)象可以動(dòng)態(tài)地改變并發(fā)狀態(tài)噪馏,你也不需要提供isConcurrent key path的KVO通知。
自定義一個(gè)Operation對(duì)象的執(zhí)行行為
對(duì)Operation對(duì)象的配置發(fā)生在創(chuàng)建對(duì)象之后绿饵,將其添加到queue之前欠肾。
配置operation之間的依賴(lài)關(guān)系
依賴(lài)關(guān)系可以順序地執(zhí)行相關(guān)的operation對(duì)象,依賴(lài)于其它操作蝴罪,則必須等到該操作完成之后自己才能開(kāi)始董济。你可以創(chuàng)建一對(duì)一的依賴(lài)關(guān)系,也可以創(chuàng)建多個(gè)對(duì)象之間的依賴(lài)圖要门。
使用 NSOperation 的 addDependency: 方法在兩個(gè)operation對(duì)象之間建立依賴(lài)關(guān)系虏肾。表示當(dāng)前operation對(duì)象將依賴(lài)于參數(shù)指定的目標(biāo)operation對(duì)象。依賴(lài)關(guān)系不局限于相同queue中的operations對(duì)象欢搜,Operation對(duì)象會(huì)管理自己的依賴(lài)封豪,因此完全可以在不同的queue之間的Operation對(duì)象創(chuàng)建依賴(lài)關(guān)系。
唯一的限制是不能創(chuàng)建環(huán)形依賴(lài)炒瘟,這是程序員的錯(cuò)誤吹埠,所有受影響的operations都無(wú)法運(yùn)行!
當(dāng)一個(gè)operation對(duì)象依賴(lài)的所有其它對(duì)象都已經(jīng)執(zhí)行完成,該operation就變成準(zhǔn)備執(zhí)行狀態(tài)(如果你自定義了isReady方法疮装,則由你的方法確定是否準(zhǔn)備好運(yùn)行)缘琅。如果operation已經(jīng)在一個(gè)queue中,queue就可以在任何時(shí)候執(zhí)行這個(gè)operation廓推。如果你需要手動(dòng)執(zhí)行該operation刷袍,就自己調(diào)用operation的start方法。
配置依賴(lài)必須在運(yùn)行operation和添加operation到queue之前進(jìn)行樊展,之后添加的依賴(lài)關(guān)系可能不起作用呻纹。
依賴(lài)要求每個(gè)operation對(duì)象在狀態(tài)發(fā)生變化時(shí)必須發(fā)出適當(dāng)?shù)腒VO通知。如果你自定義了operation對(duì)象的行為专缠,就必須在自定義代碼中生成適當(dāng)?shù)腒VO通知雷酪,以確保依賴(lài)能夠正確地執(zhí)行。
修改Operation的執(zhí)行優(yōu)先級(jí)
對(duì)于添加到queue的Operations涝婉,執(zhí)行順序首先由已入隊(duì)列的operations是否準(zhǔn)備好哥力,然后再根據(jù)所有operations的相對(duì)優(yōu)先級(jí)確定。是否準(zhǔn)備好由對(duì)象的依賴(lài)關(guān)系確定墩弯,優(yōu)先級(jí)等級(jí)則是operation對(duì)象本身的一個(gè)屬性吩跋。默認(rèn)所有operation都擁有“普通”優(yōu)先級(jí)蟀淮,不過(guò)你可以通過(guò) setQueuePriority: 方法來(lái)提升或降低operation對(duì)象的優(yōu)先級(jí)。
優(yōu)先級(jí)只能應(yīng)用于相同queue中的operations钞澳。如果應(yīng)用有多個(gè)operation queue怠惶,每個(gè)queue的優(yōu)先級(jí)等級(jí)是互相獨(dú)立的。因此不同queue中的低優(yōu)先級(jí)操作仍然可能比高優(yōu)先級(jí)操作更早執(zhí)行轧粟。
優(yōu)先級(jí)不能替代依賴(lài)關(guān)系策治,優(yōu)先級(jí)只是queue對(duì)已經(jīng)準(zhǔn)備好的operations確定執(zhí)行順序。先滿(mǎn)足依賴(lài)關(guān)系兰吟,然后再根據(jù)優(yōu)先級(jí)從所有準(zhǔn)備好的操作中選擇優(yōu)先級(jí)最高的那個(gè)執(zhí)行通惫。
修改底層線(xiàn)程的優(yōu)先級(jí)
Mac OS X 10.6之后,我們可以配置operation底層線(xiàn)程的執(zhí)行優(yōu)先級(jí)混蔼,線(xiàn)程直接由內(nèi)核管理履腋,通常優(yōu)先級(jí)高的線(xiàn)程會(huì)給予更多的執(zhí)行機(jī)會(huì)。對(duì)于operation對(duì)象惭嚣,你指定線(xiàn)程優(yōu)先級(jí)為0.0到1.0之間的某個(gè)數(shù)值遵湖,0.0表示最低優(yōu)先級(jí),1.0表示最高優(yōu)先級(jí)晚吞。默認(rèn)線(xiàn)程優(yōu)先級(jí)為0.5
要設(shè)置operation的線(xiàn)程優(yōu)先級(jí)延旧,你必須在將operation添加到queue之前,調(diào)用 setThreadPriority: 方法進(jìn)行設(shè)置槽地。當(dāng)queue執(zhí)行該operation時(shí)迁沫,默認(rèn)的start方法會(huì)使用你指定的值來(lái)修改當(dāng)前線(xiàn)程的優(yōu)先級(jí)。不過(guò)新的線(xiàn)程優(yōu)先級(jí)只在operation的main方法范圍內(nèi)有效捌蚊。其它所有代碼仍然(包括completion block)運(yùn)行在默認(rèn)線(xiàn)程優(yōu)先級(jí)集畅。
如果你創(chuàng)建了并發(fā)operation,并覆蓋了start方法缅糟,你必須自己配置線(xiàn)程優(yōu)先級(jí)挺智。
設(shè)置一個(gè)completion block
在Mac OS X 10.6之后,operation可以在主任務(wù)完成之后執(zhí)行一個(gè)completion block溺拱。你可以使用這個(gè)completion block來(lái)執(zhí)行任何不屬于主任務(wù)的工作逃贝。例如你可以使用這個(gè)block來(lái)通知相關(guān)的client谣辞,操作已經(jīng)執(zhí)行完成迫摔。而并發(fā)operation對(duì)象則可以使用這個(gè)block來(lái)產(chǎn)生最終的KVO通知。
調(diào)用 NSOperation 的 setCompletionBlock: 方法來(lái)設(shè)置一個(gè)completion block泥从,你傳遞的block應(yīng)該沒(méi)有參數(shù)和返回值句占。
實(shí)現(xiàn)Operation對(duì)象的技巧
Operation對(duì)象的內(nèi)存管理
operation對(duì)象需要良好的內(nèi)存管理策略
創(chuàng)建你自己的Autorelease Pool
operation是Objective-C對(duì)象,你在實(shí)現(xiàn)任務(wù)的代碼中應(yīng)該創(chuàng)建一個(gè)autorelease pool躯嫉,這樣可以保護(hù)那些autorelease對(duì)象得到盡快地釋放纱烘。雖然你的自定義代碼執(zhí)行時(shí)可能已經(jīng)有了一個(gè)pool杨拐,但你不能依賴(lài)于這個(gè)行為,總是應(yīng)該自己創(chuàng)建一個(gè)擂啥。
擁有自己的autorelease pool還能更加靈活地管理operation的內(nèi)存哄陶。如果operation創(chuàng)建大量的臨時(shí)對(duì)象,則可以考慮創(chuàng)建額外的pool哺壶,來(lái)清理不再使用的臨時(shí)對(duì)象屋吨。在iOS*****別需要注意,應(yīng)遲早地清理不再使用的臨時(shí)對(duì)象山宾,避免內(nèi)存警告至扰。
- (void)main {
@try {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Do the main work of the operation here.
[pool release];
}
@catch(...) {
// Do not rethrow exceptions.
}
}
避免Per-Thread存儲(chǔ)
雖然多數(shù)operation都在線(xiàn)程中執(zhí)行,但對(duì)于非并發(fā)operation资锰,通常由operation queue提供線(xiàn)程敢课,這時(shí)候queue擁有該線(xiàn)程,而你的應(yīng)用不應(yīng)該去動(dòng)這個(gè)線(xiàn)程绷杜。特別是不要關(guān)聯(lián)任何數(shù)據(jù)到不是你創(chuàng)建和擁有的線(xiàn)程直秆。這些線(xiàn)程由queue管理,根據(jù)系統(tǒng)和應(yīng)用的需求創(chuàng)建或銷(xiāo)毀鞭盟。因此使用Per-Thread storage在operations之間傳遞數(shù)據(jù)是不可靠的切厘,而且很有可能會(huì)失敗。
對(duì)于operation對(duì)象懊缺,你完全沒(méi)有理由使用Per-Thread Storage疫稿,應(yīng)該在創(chuàng)建對(duì)象的時(shí)候就給它需要的所有數(shù)據(jù)。所有輸入和輸出數(shù)據(jù)都應(yīng)該存儲(chǔ)在operation對(duì)象中鹃两,最后再整合到你的應(yīng)用遗座,或者最終釋放掉。
根據(jù)需要保留Operation對(duì)象的引用
由于operation對(duì)象異步執(zhí)行俊扳,你不能創(chuàng)建完以后就完全不管途蒋。它們也是對(duì)象,需要你來(lái)分配和釋放它們管理的任何資源馋记,特別是如果你需要在operation對(duì)象完成后獲取其中的數(shù)據(jù)号坡。
由于queue總是盡最大可能快速地調(diào)度和執(zhí)行operation,在你添加operation到queue時(shí)梯醒,可能立即就開(kāi)始運(yùn)行宽堆,當(dāng)你稍后向queue請(qǐng)求operation對(duì)象的狀態(tài)時(shí),有可能queue已經(jīng)執(zhí)行完了相應(yīng)的operation并從queue中刪除了這個(gè)對(duì)象茸习。因此你總是應(yīng)該自己擁有operation對(duì)象的引用畜隶。
處理錯(cuò)誤和異常
operation本質(zhì)上是應(yīng)用中獨(dú)立的實(shí)體,因此需要自己負(fù)責(zé)處理所有的錯(cuò)誤和異常。NSOperation默認(rèn)的start方法并沒(méi)有捕獲異常籽慢。所以你自己的代碼總是應(yīng)該捕獲并抑制異常浸遗。你還應(yīng)該檢查錯(cuò)誤代碼并適當(dāng)?shù)赝ㄖ獞?yīng)用。如果你覆蓋了start方法箱亿,你也必須捕獲所有異常跛锌,阻止它離開(kāi)底層線(xiàn)程的范圍。
你需要準(zhǔn)備好處理以下錯(cuò)誤或異常:
檢查并處理UNIX errno風(fēng)格的錯(cuò)誤代碼
檢查方法或函數(shù)顯式返回的錯(cuò)誤代碼
捕獲你的代碼或系統(tǒng)frameworks拋出的異常
捕獲NSOperation類(lèi)自己拋出的異常届惋,在以下情況NSOperation會(huì)拋出異常:
operation沒(méi)有準(zhǔn)備好察净,但是調(diào)用了start方法
operation正在執(zhí)行或已經(jīng)完成(可能被取消),再次調(diào)用了start方法盼樟。
當(dāng)你添加completion block到正在執(zhí)行或已經(jīng)完成的operation
當(dāng)你試圖獲取已經(jīng)取消 NSInvocationOperation 對(duì)象的結(jié)果
為Operation對(duì)象確定一個(gè)適當(dāng)?shù)姆秶?/p>
和任何對(duì)象一樣氢卡,NSOperation對(duì)象也會(huì)消耗內(nèi)存,執(zhí)行時(shí)也會(huì)帶來(lái)開(kāi)銷(xiāo)晨缴。因此如果operation對(duì)象只做很少的工作译秦,但是卻創(chuàng)建成千上萬(wàn)個(gè)小的operation對(duì)象,你就會(huì)發(fā)現(xiàn)更多的時(shí)間花在了調(diào)度operations而不是執(zhí)行它們击碗。
要高效地使用Operations筑悴,關(guān)鍵是在Operation執(zhí)行的工作量和保持計(jì)算機(jī)繁忙之間,找到最佳的平衡稍途。確保每個(gè)Operation都有一定的工作量可以執(zhí)行阁吝。例如100個(gè)operations執(zhí)行100次相同任務(wù),可以考慮換成10個(gè)operations械拍,每個(gè)執(zhí)行10次突勇。
你同樣要避免向一個(gè)queue中添加過(guò)多的operations,或者持續(xù)快速地向queue中添加operation坷虑,超過(guò)queue所能處理的能力甲馋。這里可以考慮分批創(chuàng)建operations對(duì)象,在一批對(duì)象執(zhí)行完之后迄损,使用completion block告訴應(yīng)用創(chuàng)建下一批operations對(duì)象定躏。
執(zhí)行Operations
應(yīng)用需要執(zhí)行Operations來(lái)處理相關(guān)的工作,你有幾種方法來(lái)執(zhí)行Operations對(duì)象芹敌。
添加Operations到Operation Queue
執(zhí)行Operations最簡(jiǎn)單的方法是添加到operation queue痊远,后者是 NSOperationQueue 對(duì)象。應(yīng)用負(fù)責(zé)創(chuàng)建和維護(hù)自己使用的所有 NSOperationQueue 對(duì)象氏捞。
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
調(diào)用 addOperation: 方法添加一個(gè)operation到queue碧聪,Mac OS X 10.6之后可以使用 addOperations:waitUntilFinished: 方法一次添加一組operations,或者也可以直接使用 addOperationWithBlock: 方法添加 block 對(duì)象到queue幌衣。
[aQueue addOperation:anOp]; // Add a single operation
[aQueue addOperations:anArrayOfOps waitUntilFinished:NO]; // Add multiple operations
[aQueue addOperationWithBlock:^{
/* Do something. */
}];
Operations添加到queue后矾削,通常短時(shí)間內(nèi)就會(huì)得到運(yùn)行。但是如果存在依賴(lài)豁护,或者Operations掛起等原因哼凯,也可能需要等待。
注意Operations添加到queue之后楚里,絕對(duì)不要再修改Operations對(duì)象断部。因?yàn)镺perations對(duì)象可能會(huì)在任何時(shí)候運(yùn)行,因此改變依賴(lài)或數(shù)據(jù)會(huì)產(chǎn)生不利的影響班缎。你只能通過(guò) NSOperation 的方法來(lái)查看操作的狀態(tài)蝴光,是否正在運(yùn)行、等待運(yùn)行达址、已經(jīng)完成等蔑祟。
雖然 NSOperationQueue 類(lèi)設(shè)計(jì)用于并發(fā)執(zhí)行Operations,你也可以強(qiáng)制單個(gè)queue一次只能執(zhí)行一個(gè)Operation沉唠。setMaxConcurrentOperationCount: 方法可以配置operation queue的最大并發(fā)操作數(shù)量疆虚。設(shè)為1就表示queue每次只能執(zhí)行一個(gè)操作。不過(guò)operation執(zhí)行的順序仍然依賴(lài)于其它因素满葛,像操作是否準(zhǔn)備好和優(yōu)先級(jí)等径簿。因此串行化的operation queue并不等同于Grand Central Dispatch中的串行dispatch queue。
手動(dòng)執(zhí)行Operations
手動(dòng)執(zhí)行Operation嘀韧,要求Operation已經(jīng)準(zhǔn)備好篇亭,isReady返回YES,此時(shí)你才能調(diào)用start方法來(lái)執(zhí)行它锄贷。isReady方法與Operations依賴(lài)是結(jié)合在一起的译蒂。
調(diào)用start而不是main來(lái)手動(dòng)執(zhí)行Operation,因?yàn)閟tart在執(zhí)行你的自定義代碼之前谊却,會(huì)首先執(zhí)行一些安全檢查蹂随。而且start還會(huì)產(chǎn)生KVO通知,以正確地支持Operations的依賴(lài)機(jī)制因惭。start還能處理Operations已經(jīng)被取消的情況岳锁,此時(shí)會(huì)拋出一個(gè)異常。
手動(dòng)執(zhí)行Operation對(duì)象之前蹦魔,還需要調(diào)用 isConcurrent 方法激率,如果返回NO,你的代碼可以決定在當(dāng)前線(xiàn)程同步執(zhí)行這個(gè)Operation勿决,或者創(chuàng)建一個(gè)獨(dú)立的線(xiàn)程以異步執(zhí)行乒躺。
下面方法演示了手動(dòng)執(zhí)行Operation,如果這個(gè)方法返回NO低缩,表示不能執(zhí)行嘉冒,你需要設(shè)置一個(gè)定時(shí)器曹货,稍后再次調(diào)用本方法,直到這個(gè)方法返回YES讳推,表示已經(jīng)執(zhí)行Operation愕鼓。
- (BOOL)performOperation:(NSOperation*)anOp
{
BOOL ranIt = NO;
if ([anOp isReady] && ![anOp isCancelled])
{
if (![anOp isConcurrent])
[anOp start];
else
[NSThread detachNewThreadSelector:@selector(start)
toTarget:anOp withObject:nil];
ranIt = YES;
}
else if ([anOp isCancelled])
{
// If it was canceled before it was started,
// move the operation to the finished state.
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
// Set ranIt to YES to prevent the operation from
// being passed to this method again in the future.
ranIt = YES;
}
return ranIt;
}
取消Operations
一旦添加到operation queue露懒,queue就擁有了這個(gè)對(duì)象并且不能被刪除,唯一能做的事情是取消。你可以調(diào)用Operation對(duì)象的cancel方法取消單個(gè)操作崇呵,也可以調(diào)用operation queue的 cancelAllOperations 方法取消當(dāng)前queue中的所有操作日裙。
只有你確定不再需要Operations對(duì)象時(shí)弦疮,才應(yīng)該取消它阻肩。發(fā)出取消命令會(huì)將Operations對(duì)象設(shè)置為"Canceled"狀態(tài),會(huì)阻止它被執(zhí)行洒忧。由于取消也被認(rèn)為是完成蝴韭,依賴(lài)于它的其它Operations對(duì)象會(huì)收到適當(dāng)?shù)腒VO通知,并清除依賴(lài)狀態(tài)熙侍,然后得到執(zhí)行万皿。
因此常見(jiàn)的做法是當(dāng)發(fā)生重大事件時(shí),一次性取消queue中的所有操作核行,例如應(yīng)用退出或用戶(hù)請(qǐng)求取消操作牢硅。
等待Operations完成
為了最佳的性能,你應(yīng)該盡量設(shè)計(jì)你的應(yīng)用盡可能地異步操作芝雪,讓?xiě)?yīng)用在操作正在執(zhí)行時(shí)可以去處理其它事情减余。
如果創(chuàng)建operation的代碼需要處理operation完成后的結(jié)果,可以使用 NSOperation 的 waitUntilFinished 方法等待operation完成惩系。通常我們應(yīng)該避免編寫(xiě)這樣的代碼位岔,阻塞當(dāng)前線(xiàn)程可能是一種簡(jiǎn)便的解決方案,但是它引入了更多的串行代碼堡牡,限制了整個(gè)應(yīng)用的并發(fā)性抒抬,同時(shí)也降低了用戶(hù)體驗(yàn)。
絕對(duì)不要在應(yīng)用主線(xiàn)程中等待一個(gè)Operation晤柄,只能在第二或次要線(xiàn)程中等待擦剑。阻止主線(xiàn)程將導(dǎo)致應(yīng)用無(wú)法響應(yīng)用戶(hù)事件,應(yīng)用也將表現(xiàn)為無(wú)響應(yīng)芥颈。
除了等待單個(gè)Operation完成惠勒,你也可以同時(shí)等待一個(gè)queue中的所有操作,使用 NSOperationQueue 的 waitUntilAllOperationsAreFinished 方法爬坑。注意在等待一個(gè)queue時(shí)纠屋,應(yīng)用的其它線(xiàn)程仍然可以往queue中添加Operation,因此可能加長(zhǎng)你線(xiàn)程的等待時(shí)間盾计。
掛起和繼續(xù)Queue
如果你想臨時(shí)掛起Operations的執(zhí)行售担,可以使用 setSuspended: 方法暫停相應(yīng)的queue赁遗。不過(guò)掛起一個(gè)queue不會(huì)導(dǎo)致正在執(zhí)行的Operation在任務(wù)中途暫停,只是簡(jiǎn)單地阻止調(diào)度新Operation執(zhí)行族铆。你可以在響應(yīng)用戶(hù)請(qǐng)求時(shí)岩四,掛起一個(gè)queue,來(lái)暫停等待中的任務(wù)骑素。稍后根據(jù)用戶(hù)的請(qǐng)求炫乓,可以再次調(diào)用 setSuspended: 方法繼續(xù)Queue中操作的執(zhí)行刚夺。