前言
和NSThread德频、GCD一樣姊扔,NSOperation也是Apple提供的一項(xiàng)多線程并發(fā)編程方案蝶怔。和GCD不同的是伟叛,NSOperation是對(duì)GCD在OC層面的封裝勃教,NSOperation完全面向?qū)ο蟆?br> 默認(rèn)情況下淤击,NSOperation并不具備封裝操作的能力,NSOperation是一個(gè)基類故源,要想封裝操作污抬,必須使用它的子類。使用NSOperation子類的方式有3種:
1> NSInvocationOperation(系統(tǒng)提供)
2> NSBlockOperation (系統(tǒng)提供)
3> 自定義子類繼承NSOperation绳军,實(shí)現(xiàn)內(nèi)部相應(yīng)的方法 (自定義)
(一) NSInvocationOperation
NSInvocationOperation初始化的方法有兩個(gè)印机,分別如下:
- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
- (instancetype)initWithInvocation:(NSInvocation *)inv NS_DESIGNATED_INITIALIZER;
(1.1) initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
[op start];
解析:
上面通過(guò)傳入一個(gè)方法簽名(selector)和方法調(diào)用者(target)初始化了一個(gè)NSInvocationOperation對(duì)象。
調(diào)用start實(shí)例方法可以執(zhí)行該操作封裝的任務(wù)门驾。
(1.2) initWithInvocation:(NSInvocation *)inv
NSMethodSignature *sign = [[self class] instanceMethodSignatureForSelector:@selector(demo)];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sign];
inv.target = self;
inv.selector = @selector(demo);
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithInvocation:inv];
[op2 start];
解析:
上面通過(guò)傳入一個(gè)NSInvocation對(duì)象初始化了一個(gè)NSInvocationOperation對(duì)象射赛。NSInvocation對(duì)象通過(guò)傳入一個(gè)方法簽名進(jìn)行初始化,并且給NSInvocation對(duì)象設(shè)置了target和selector奶是。
注意:NSInvocationOperation實(shí)例對(duì)象直接調(diào)用start方法是在當(dāng)前線程執(zhí)行操作封裝的任務(wù)楣责。而不是在子線程中執(zhí)行。也就是說(shuō)聂沙,NSInvocationOperation實(shí)例對(duì)象直接調(diào)用start方法不會(huì)開(kāi)啟新線程異步執(zhí)行秆麸,而是同步執(zhí)行。只有將NSInvocationOperation實(shí)例對(duì)象添加到一個(gè)NSOperationQueue中及汉,才會(huì)異步執(zhí)行操作沮趣。
(二) NSBlockOperation
NSBlockOperation是NSOperation的子類。NSBlockOperation中給我們提供了兩個(gè)方法:
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;
第一個(gè)是類方法(靜態(tài)方法)坷随,可以通過(guò)類方法直接初始化一個(gè)blockOperation對(duì)象房铭。
第二個(gè)是實(shí)例方法(對(duì)象方法/動(dòng)態(tài)方法),可以給一個(gè)已經(jīng)存在的NSBlockOperation對(duì)象添加額外的任務(wù)甸箱。
和NSInvocationOperation相比育叁,NSBlockOperation對(duì)象不用添加到操作隊(duì)列也能開(kāi)啟新線程,但是開(kāi)啟新線程是有條件的芍殖。前提是一個(gè)blockOperation中需要封裝多個(gè)任務(wù)。如下示例谴蔑,blockOperation中只有一個(gè)任務(wù)豌骏,默認(rèn)會(huì)在當(dāng)前線程執(zhí)行。
// 同步執(zhí)行
NSBlockOperation *blkop = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
[blkop start];
// 輸出結(jié)果:
NSOperation[1839:133702] <NSThread: 0x608000076b00>{number = 1, name = main}
解析:
NSBlockOperation類通過(guò)調(diào)用類方法blockOperationWithBlock:
直接初始化一個(gè)NSBlockOperation對(duì)象隐锭。其中類方法需要一個(gè)block作為參數(shù)窃躲,該block中封裝的就是這個(gè)NSBlockOperation對(duì)象要執(zhí)行的任務(wù)。然后直接調(diào)用start實(shí)例方法即可觸發(fā)操作的執(zhí)行钦睡。無(wú)需將NSBlockOperation對(duì)象加入到操作隊(duì)列中蒂窒。
注意:NSBlockOperation對(duì)象如果只封裝了一個(gè)任務(wù), 那么默認(rèn)會(huì)在當(dāng)前線程中同步執(zhí)行。
// 異步執(zhí)行
NSBlockOperation *blkop = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)1- %@", [NSThread currentThread]);
}];
// 添加額外的任務(wù)
[blkop addExecutionBlock:^{
NSLog(@"任務(wù)2- %@", [NSThread currentThread]);
}];
[blkop addExecutionBlock:^{
NSLog(@"任務(wù)3- %@", [NSThread currentThread]);
}];
[blkop start];
// 輸出結(jié)果:
2017-02-08 22:41:54.871 NSOperation[1884:142063] 任務(wù)1- <NSThread: 0x60800007cec0>{number = 1, name = main}
2017-02-08 22:41:54.871 NSOperation[1884:142100] 任務(wù)3- <NSThread: 0x6080002699c0>{number = 4, name = (null)}
2017-02-08 22:41:54.871 NSOperation[1884:142101] 任務(wù)2- <NSThread: 0x608000269800>{number = 3, name = (null)}
解析:
初始化一個(gè)NSBlockOperation對(duì)象,然后調(diào)用addExecutionBlock:
對(duì)象方法給這個(gè)NSBlockOperation對(duì)象添加額外的任務(wù)洒琢。
注意:
一般情況下
秧秉,如果一個(gè)NSBlockOperation對(duì)象封裝了多個(gè)任務(wù)。那么除第一個(gè)任務(wù)外衰抑,其他的任務(wù)會(huì)在新線程(子線程)中執(zhí)行象迎。即,NSBlockOperation是否開(kāi)啟新線程取決于任務(wù)的個(gè)數(shù)呛踊,任務(wù)的個(gè)數(shù)多砾淌,會(huì)自動(dòng)開(kāi)啟新線程。但是第一個(gè)被執(zhí)行的任務(wù)是同步執(zhí)行谭网,除第一個(gè)任務(wù)外汪厨,其他任務(wù)是異步執(zhí)行的。
(三) 自定義NSOperation
如果NSInvocationOperation和NSBlockOperation不能滿足需求愉择。你可以通過(guò)重寫 main 或者 start 方法 來(lái)定義自己的 operations 骄崩。前一種方法非常簡(jiǎn)單,開(kāi)發(fā)者不需要管理一些狀態(tài)屬性(例如 isExecuting 和 isFinished)薄辅,當(dāng) main 方法返回的時(shí)候要拂,這個(gè) operation 就結(jié)束了。這種方式使用起來(lái)非常簡(jiǎn)單站楚,但是靈活性相對(duì)重寫 start 來(lái)說(shuō)要少一些脱惰。
引自并發(fā)編程:API 及挑戰(zhàn)。如果只是簡(jiǎn)單地自定義NSOperation窿春,只需要重載-(void)main這個(gè)方法拉一,在這個(gè)方法里面添加需要執(zhí)行的操作。
// EOCOperation.h
#import <Foundation/Foundation.h>
@interface EOCOperation : NSOperation
@end
// EOCOperation.m
#import "EOCOperation.h"
@implementation EOCOperation
- (void)main {
NSLog(@"%@",[NSThread currentThread]);
}
@end
// 調(diào)用自定義operation
EOCOperation *customOperation = [[EOCOperation alloc] init];
[customOperation start];
輸出結(jié)果:
NSOperation[2084:169435] <NSThread: 0x600000260f80>{number = 1, name = main}
EOCOperation *customOperation = [[EOCOperation alloc] init];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:customOperation];
// 輸出結(jié)果:
NSOperation[739:22292] <NSThread: 0x600000070280>{number = 3, name = (null)}
自定義operation和NSInvocationOperation一樣旧乞,如果直接調(diào)用start方法蔚润,不把operation添加到操作隊(duì)列中,任務(wù)直接在當(dāng)前線程同步執(zhí)行尺栖。
如果把自定義operation添加到操作隊(duì)列嫡纠,那么任務(wù)會(huì)在新線程中異步執(zhí)行。
警告:
不要即把操作添加到操作隊(duì)列中延赌,又調(diào)用操作的start方法除盏,這樣是不允許的!否則運(yùn)行時(shí)直接報(bào)錯(cuò)挫以。
NSOperation[756:24507] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSOperationInternal _start:]: something other than the operation queue it is in is trying to start the receiver'
為了能夠使用操作隊(duì)列提供的取消功能者蠕,我們需要在main方法中經(jīng)常性的判斷操作有沒(méi)有被取消,如果操作已經(jīng)被取消掐松,我們需要立即使main方法返回踱侣,不再執(zhí)行后續(xù)代碼粪小。在以下情況可能需要判斷操作是否已經(jīng)取消:
- main方法的開(kāi)頭。因?yàn)槿∠赡馨l(fā)生在任何時(shí)候抡句,甚至在operation執(zhí)行之前探膊。
- 執(zhí)行了一段比較耗時(shí)的操作后。因?yàn)閳?zhí)行耗時(shí)操作期間有可能取消了該操作玉转。
- 其他任何有可能的地方突想。
舉例來(lái)講:自定義operation的main函數(shù)中需要封裝網(wǎng)絡(luò)請(qǐng)求的URL,然后拼接參數(shù)究抓。然后發(fā)送一個(gè)異步請(qǐng)求猾担,請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)。我們需要在以下地方進(jìn)行判斷是否已經(jīng)取消操作刺下。
- (void)main {
if (self.isCancelled) {
return;
}
// 封裝URL
......
if (self.isCancelled) {
return;
}
// 拼接參數(shù)
......
if (self.isCancelled) {
return;
}
// 異步請(qǐng)求
......
if (self.isCancelled) {
return;
}
}
如果你希望擁有更多的控制權(quán)绑嘹,以及在一個(gè)操作中可以執(zhí)行異步任務(wù),那么就重寫 start 方法:
- (void)start
{
self.isExecuting = YES;
self.isFinished = NO;
// 開(kāi)始處理橘茉,在結(jié)束時(shí)應(yīng)該調(diào)用 finished ...
}
- (void)finished
{
self.isExecuting = NO;
self.isFinished = YES;
}
注意:
這種情況下工腋,你必須手動(dòng)管理操作的狀態(tài)。 為了讓操作隊(duì)列能夠捕獲到操作的改變畅卓,需要將狀態(tài)的屬性以配合 KVO 的方式進(jìn)行實(shí)現(xiàn)擅腰。如果你不使用它們默認(rèn)的 setter 來(lái)進(jìn)行設(shè)置的話,你就需要在合適的時(shí)候發(fā)送合適的 KVO 消息翁潘。
(四) NSOperation其他方法
(4.1) cancel方法
NSOperation除了有start方法趁冈,還有cancel方法。我們可以調(diào)用cancel方法取消未執(zhí)行的操作拜马。但是已執(zhí)行或者正在執(zhí)行的操作不可取消渗勘。
即便操作已經(jīng)被添加到操作隊(duì)列中也可以取消,只要操作沒(méi)有開(kāi)始被執(zhí)行俩莽。
因?yàn)楣俜轿臋n上是這么說(shuō)的:This method does not force your operation code to stop. Instead, it updates the object’s internal flags to reflect the change in state. If the operation has already finished executing, this method has no effect. Canceling an operation that is currently in an operation queue, but not yet executing, makes it possible to remove the operation from the queue sooner than usual.
// 1.封裝op1
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
// 封裝op2
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"執(zhí)行op2%@",[NSThread currentThread]);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op2];
// 取消op2
[op2 cancel];
NSLog(@"執(zhí)行op1- %@", [NSThread currentThread]);
}];
[op1 start];
// 輸出結(jié)果:
2017-02-09 19:46:24.745 NSOperation[1163:67542] 執(zhí)行op1- <NSThread: 0x6000000770c0>{number = 1, name = main}
解析:
上面代碼只會(huì)執(zhí)行op1旺坠,op2永遠(yuǎn)也不會(huì)執(zhí)行,因?yàn)樵趏p2執(zhí)行之前就已經(jīng)通過(guò)調(diào)用了cancel方法扮超,取消了op2的執(zhí)行取刃。所以輸出結(jié)果只有執(zhí)行op1。且op1是在主線程執(zhí)行的瞒津。
如果我們不取消op2蝉衣,那么op2也會(huì)被執(zhí)行。只需要注釋掉取消op2的代碼巷蚪。
注意:
我們可以通過(guò)調(diào)用cancel方法取消某個(gè)尚未執(zhí)行的操作(無(wú)論這個(gè)操作是否被加入了操作隊(duì)列)。但是我們不能取消正在執(zhí)行或者已經(jīng)執(zhí)行完的操作濒翻。
(4.2) completionBlock屬性
NSOperation提供了一個(gè)block類型的completionBlock屬性屁柏。如果想在操作執(zhí)行完畢之后啦膜,還希望做一些其他的事情,可以通過(guò)completionBlock實(shí)現(xiàn)淌喻。
無(wú)論操作是直接調(diào)用start執(zhí)行還是加入到操作隊(duì)列中執(zhí)行僧家,也無(wú)論操作是同步執(zhí)行還是異步執(zhí)行。completionBlock永遠(yuǎn)是等待操作所有任務(wù)執(zhí)行完畢最后被調(diào)用裸删。
同步執(zhí)行
NSBlockOperation *blkop = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"執(zhí)行%@",[NSThread currentThread]);
}];
blkop.completionBlock = ^{
NSLog(@"完成");
};
[blkop start];
// 輸出結(jié)果:
2017-02-09 20:03:30.387 NSOperation[1395:94883] 執(zhí)行<NSThread: 0x600000065d40>{number = 1, name = main}
2017-02-09 20:03:30.388 NSOperation[1395:94930] 完成
異步執(zhí)行
NSBlockOperation *blkop = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"執(zhí)行%@",[NSThread currentThread]);
}];
blkop.completionBlock = ^{
NSLog(@"完成");
};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:blkop];
// 輸出結(jié)果:
2017-02-09 20:00:05.145 NSOperation[1364:91326] 執(zhí)行<NSThread: 0x60800007d500>{number = 3, name = (null)}
2017-02-09 20:00:05.146 NSOperation[1364:91329] 完成
給操作設(shè)置completionBlock八拱,必須要在操作被執(zhí)行前添加,也就是在操作start之前添加涯塔,以下的做法是錯(cuò)誤的:
NSBlockOperation *blkop = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"執(zhí)行%@",[NSThread currentThread]);
}];
[blkop start];
blkop.completionBlock = ^{
NSLog(@"完成");
};
如果一個(gè)操作是被加到操作隊(duì)列中肌稻,然后才設(shè)置completionBlock,這樣是可以的匕荸,如下:
NSBlockOperation *blkop = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"執(zhí)行%@",[NSThread currentThread]);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:blkop];
blkop.completionBlock = ^{
NSLog(@"完成");
};
總結(jié):
如果操作是通過(guò)調(diào)用start方法觸發(fā)的爹谭,那么completionBlock必須要在start之前設(shè)置。如果操作是通過(guò)加入操作隊(duì)列被觸發(fā)的榛搔,那么completionBlock可以在操作添加到操作隊(duì)列之后設(shè)置诺凡,只要保證此時(shí)操作沒(méi)有被執(zhí)行即可。
(4.3) 給NSOperation添加Dependency
默認(rèn)操作的執(zhí)行是無(wú)序的践惑,NSOperation之間可以通過(guò)設(shè)置依賴來(lái)保證操作執(zhí)行的順序腹泌。
比如一定要讓操作A執(zhí)行完后,才能執(zhí)行操作B尔觉,可以這么寫:
[operationB addDependency:operationA];
操作B依賴于操作A凉袱,所以一定要等操作A執(zhí)行完畢才能執(zhí)行操作B。
操作的執(zhí)行順序不是取決于誰(shuí)先被添加到隊(duì)列中穷娱,而是取決于操作依賴绑蔫。也就是說(shuō),添加順序不會(huì)決定執(zhí)行順序泵额,只有依賴才會(huì)決定執(zhí)行順序(maxConcurrentOperationCount = 1除外配深,因?yàn)閙axConcurrentOperationCount = 1時(shí),操作隊(duì)列為串行隊(duì)列嫁盲,如果沒(méi)有給操作添加依賴篓叶,此時(shí)操作的執(zhí)行順序取決于操作添加到隊(duì)列中的先后順序。即便如此羞秤,maxConcurrentOperationCount = 1時(shí)缸托,隊(duì)列中的操作也并不一定在同一個(gè)線程中執(zhí)行)。
即操作依賴可以控制操作的執(zhí)行順序瘾蛋,使多個(gè)并行的操作可以按照串行的順序一個(gè)一個(gè)地執(zhí)行俐镐。如果沒(méi)有給操作添加依賴,設(shè)置操作隊(duì)列的maxConcurrentOperationCount = 1也可以控制操作的執(zhí)行順序哺哼,其執(zhí)行順序取決于操作添加到隊(duì)列中的順序佩抹。
如果操作設(shè)置了依賴叼风,也給隊(duì)列設(shè)置了maxConcurrentOperationCount = 1。那么操作被執(zhí)行的順序取決于依賴棍苹。即无宿,依賴的優(yōu)先級(jí)較高。
NSBlockOperation *blkop1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"執(zhí)行1 %@",[NSThread currentThread]);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
NSBlockOperation *blkop2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"執(zhí)行2 %@",[NSThread currentThread]);
}];
[blkop1 addDependency:blkop2];
[queue addOperation:blkop1];
[queue addOperation:blkop2];
// 輸出結(jié)果:
2017-02-09 20:57:13.369 NSOperation[2371:194728] 執(zhí)行2 <NSThread: 0x600000267700>{number = 3, name = (null)}
2017-02-09 20:57:13.375 NSOperation[2371:194725] 執(zhí)行1 <NSThread: 0x6000002615c0>{number = 4, name = (null)}
解析:
雖然設(shè)置了隊(duì)列的maxConcurrentOperationCount = 1枢里,使操作隊(duì)列變成一個(gè)串行隊(duì)列孽鸡。但是也設(shè)置了操作之間的依賴,所以最終操作的執(zhí)行順序取決于依賴栏豺。所以上面的執(zhí)行結(jié)果永遠(yuǎn)是先執(zhí)行操作2彬碱,再執(zhí)行操作1。
注意:
一定要在操作添加到隊(duì)列之前設(shè)置操作之間的依賴冰悠,否則操作已經(jīng)添加到隊(duì)列中在設(shè)置依賴堡妒,依賴不會(huì)生效。
問(wèn)題:默認(rèn)情況下溉卓,操作隊(duì)列中的操作的執(zhí)行順序真的是無(wú)序的嗎皮迟?
個(gè)人認(rèn)為,默認(rèn)情況下桑寨,操作隊(duì)列中的操作執(zhí)行順序就是其被取出的順序伏尼,也是其被添加到隊(duì)列中的順序,操作的執(zhí)行順序是有序的尉尾,但是操作執(zhí)行完成的順序是無(wú)需的爆阶。也就是說(shuō),因?yàn)椴煌牟僮鲌?zhí)行完成所需要的時(shí)間不同沙咏,最先從對(duì)壘中取出執(zhí)行的操作不一定先執(zhí)行完成辨图,后執(zhí)行的操作不一定后執(zhí)行完成。所以肢藐,給人的感覺(jué)就是操作的執(zhí)行是無(wú)序的故河。
其實(shí),操作的依賴特性可以用GCD的信號(hào)量機(jī)制來(lái)實(shí)現(xiàn)吆豹。
不同隊(duì)列的操作之間也可以設(shè)置依賴
依賴關(guān)系不局限于相同queue中的NSOperation對(duì)象,NSOperation對(duì)象會(huì)管理自己的依賴, 因此不同的操作隊(duì)列之間的操作也可以設(shè)置依賴鱼的。如下:
NSBlockOperation *blkop1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"執(zhí)行1 %@",[NSThread currentThread]);
}];
NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
queue1.maxConcurrentOperationCount = 1;
NSBlockOperation *blkop2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"執(zhí)行2 %@",[NSThread currentThread]);
}];
[blkop2 addDependency:blkop1];
[queue1 addOperation:blkop1];
[queue1 addOperation:blkop2];
NSBlockOperation *blkop3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"執(zhí)行3 %@",[NSThread currentThread]);
}];
[blkop3 addDependency:blkop2];
NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
queue2.maxConcurrentOperationCount = 1;
[queue2 addOperation:blkop3];
// 輸出結(jié)果:
2017-02-09 21:20:52.270 NSOperation[2545:217909] 執(zhí)行1 <NSThread: 0x60000007a480>{number = 3, name = (null)}
2017-02-09 21:20:52.272 NSOperation[2545:217909] 執(zhí)行2 <NSThread: 0x60000007a480>{number = 3, name = (null)}
2017-02-09 21:20:52.273 NSOperation[2545:217907] 執(zhí)行3 <NSThread: 0x60000007a080>{number = 4, name = (null)}
解析:
上面代碼中,不僅隊(duì)列queue1中的兩個(gè)操作blkop1和blkop2間設(shè)置了依賴痘煤。兩個(gè)不同的操作隊(duì)列queue1和queue2之間的操作blkop2和blkop3也設(shè)置了依賴凑阶。最中依賴順序是:blkop2 依賴 blkop1,blkop3依賴blkop2衷快。所以操作的執(zhí)行順序永遠(yuǎn)是1宙橱、2、3。
NSOperation提供了如下三個(gè)接口管理自己的依賴:
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
警告:
操作間不能循環(huán)依賴养匈,比如A依賴B哼勇,B依賴A都伪,這是錯(cuò)誤的呕乎。
(4.4) queuePriority
NSOperation類提供了一個(gè)queuePriority
屬性,代表操作在隊(duì)列中執(zhí)行的優(yōu)先級(jí)
陨晶。
@property NSOperationQueuePriority queuePriority;
queuePriority是一個(gè)NSOperationQueuePriority類型的枚舉值猬仁,apple為NSOperationQueuePriority類型定義了一下幾個(gè)值:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
queuePriority
默認(rèn)值是NSOperationQueuePriorityNormal
。根據(jù)實(shí)際需要我們可以通過(guò)調(diào)用queuePriority的setter方法修改某個(gè)操作的優(yōu)先級(jí)先誉。
NSBlockOperation *blkop1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"執(zhí)行blkop1");
}];
NSBlockOperation *blkop2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"執(zhí)行blkop2");
}];
// 設(shè)置操作優(yōu)先級(jí)
blkop1.queuePriority = NSOperationQueuePriorityLow;
blkop2.queuePriority = NSOperationQueuePriorityVeryHigh;
NSLog(@"blkop1 == %@",blkop1);
NSLog(@"blkop2 == %@",blkop2);
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 操作添加到隊(duì)列
[queue addOperation:blkop1];
[queue addOperation:blkop2];
NSLog(@"%@",[queue operations]);
for (NSOperation *op in [queue operations]) {
NSLog(@"op == %@",op);
}
// 輸出結(jié)果:
2017-02-12 19:36:01.149 NSOperation[1712:177976] blkop1 == <NSBlockOperation: 0x608000044440>
2017-02-12 19:36:01.150 NSOperation[1712:177976] blkop2 == <NSBlockOperation: 0x6080000444d0>
2017-02-12 19:36:01.150 NSOperation[1712:177976] (
"<NSBlockOperation: 0x608000044440>",
"<NSBlockOperation: 0x6080000444d0>"
)
2017-02-12 19:36:01.150 NSOperation[1712:177976] op == <NSBlockOperation: 0x608000044440>
2017-02-12 19:36:01.150 NSOperation[1712:177976] op == <NSBlockOperation: 0x6080000444d0>
2017-02-12 19:36:01.150 NSOperation[1712:178020] 執(zhí)行blkop1
2017-02-12 19:36:01.151 NSOperation[1712:178021] 執(zhí)行blkop2
解析:
(1.)上面創(chuàng)建了兩個(gè)blockOperation并且分別設(shè)置了優(yōu)先級(jí)湿刽。顯然blkop1的優(yōu)先級(jí)低于blkop2的優(yōu)先級(jí)。然后調(diào)用了隊(duì)列的addOperation:方法使操作入隊(duì)褐耳。最后輸出結(jié)果證明诈闺,操作在對(duì)列中的順去取決于addOperation:方法而不是優(yōu)先級(jí)。
(2.)雖然blkop2優(yōu)先級(jí)高于blkop1铃芦,但是bloop1卻先于blkop2執(zhí)行完成雅镊。所以,優(yōu)先級(jí)高的操作不一定先執(zhí)行完成刃滓。
注意:
(1.)優(yōu)先級(jí)只能應(yīng)用于相同queue中的operations仁烹。
(2.)操作的優(yōu)先級(jí)高低不等于操作在隊(duì)列中排列的順序。換句話說(shuō)咧虎,優(yōu)先級(jí)高的操作不代表一定排在隊(duì)列的前面卓缰。后入隊(duì)的操作有可能因?yàn)閮?yōu)先級(jí)高而先被執(zhí)行。PS:操作在隊(duì)列中的順序取決于隊(duì)列的addOperation:
方法砰诵。(證明代碼如下)
(3.)優(yōu)先級(jí)高只代表先被執(zhí)行征唬。不代表操作先被執(zhí)行完成。執(zhí)行完成的早晚還取決于操作耗時(shí)長(zhǎng)短茁彭。
(4.)優(yōu)先級(jí)不能替代依賴总寒,優(yōu)先級(jí)也絕不等于依賴。優(yōu)先級(jí)只是對(duì)已經(jīng)準(zhǔn)備好的操作確定其執(zhí)行順序尉间。
(5.)操作的執(zhí)行優(yōu)先滿足依賴關(guān)系偿乖,然后再滿足優(yōu)先級(jí)。即先根據(jù)依賴執(zhí)行操作哲嘲,然后再?gòu)乃袦?zhǔn)備好的操作中取出優(yōu)先級(jí)最高的那一個(gè)執(zhí)行贪薪。
(4.5) qualityOfService
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
qualityOfService
是NSOperation類提供的一個(gè)屬性。qualityOfService即服務(wù)質(zhì)量
眠副。
(五) 隊(duì)列
(5.1) 取消
一旦操作添加到operation queue中,queue就擁有了這個(gè)Operation對(duì)象并且不能被刪除画切,唯一能做的事情是取消。你可以調(diào)用Operation對(duì)象的cancel方法取消單個(gè)操作,也可以調(diào)用operation queue的cancelAllOperations方法取消當(dāng)前queue中的所有操作囱怕。
- (void)cancelAllOperations;
隊(duì)列通過(guò)調(diào)用對(duì)象方法- (void)cancelAllOperations;
可以取消隊(duì)列中尚未執(zhí)行的操作霍弹。但是正在執(zhí)行的操作不能夠取消毫别。
(5.2) 暫停、恢復(fù)
@property (getter=isSuspended) BOOL suspended;
隊(duì)列中的操作也可以暫停典格、恢復(fù)岛宦。通過(guò)調(diào)用suspended的set方法控制暫停還是恢復(fù)。如果傳入YES耍缴,代表暫停砾肺,傳入NO代表恢復(fù)。
(5.3) 主隊(duì)列
@property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0);
操作隊(duì)列給我們提供了獲取主隊(duì)列的屬性mainQueue防嗡。如果想讓某些操作在主線程執(zhí)行变汪,可以直接把操作添加到mainQueue中。
(5.4) maxConcurrentOperationCount
maxConcurrentOperationCount代表隊(duì)列同一時(shí)間允許執(zhí)行的最多的任務(wù)數(shù)蚁趁∪苟埽或者理解為同一時(shí)間允許執(zhí)行的最多線程數(shù)。
maxConcurrentOperationCount默認(rèn)為-1他嫡,代表不限制番官。
maxConcurrentOperationCount 必須要提前設(shè)置,如果隊(duì)列中添加了操作再設(shè)置maxConcurrentOperationCount就無(wú)效了涮瞻。
警告:
如果希望操作在主線程中執(zhí)行鲤拿,不要設(shè)置maxConcurrentOperationCount = 0。直接把操作添加到mainQueue中即可署咽。
(5.5) waitUntilAllOperationsAreFinished
為了最佳的性能,你應(yīng)該設(shè)計(jì)你的應(yīng)用盡可能地異步操作近顷,讓應(yīng)用在Operation正在執(zhí)行時(shí)可以去處理其它事情。如果需要在當(dāng)前線程中處理operation完成后的結(jié)果,可以使用NSOperation的waitUntilFinished方法阻塞當(dāng)前線程宁否,等待operation完成窒升。通常我們應(yīng)該避免編寫這樣的代碼,阻塞當(dāng)前線程可能是一種簡(jiǎn)便的解決方案,但是它引入了更多的串行代碼,限制了整個(gè)應(yīng)用的并發(fā)性,同時(shí)也降低了用戶體驗(yàn)。絕對(duì)不要在應(yīng)用主線程中等待一個(gè)Operation,只能在第二或次要線程中等待慕匠。阻塞主線程將導(dǎo)致應(yīng)用無(wú)法響應(yīng)用戶事件,應(yīng)用也將表現(xiàn)為無(wú)響應(yīng)饱须。
// 會(huì)阻塞當(dāng)前線程,等到某個(gè)operation執(zhí)行完畢
[operation waitUntilFinished];
除了等待單個(gè)Operation完成,你也可以同時(shí)等待一個(gè)queue中的所有操作,使用NSOperationQueue的waitUntilAllOperationsAreFinished方法台谊。注意:在等待一個(gè) queue時(shí),應(yīng)用的其它線程仍然可以往queue中添加Operation,因此可能會(huì)加長(zhǎng)線程的等待時(shí)間蓉媳。
// 阻塞當(dāng)前線程,等待queue的所有操作執(zhí)行完畢
[queue waitUntilAllOperationsAreFinished];
注意:
waitUntilAllOperationsAreFinished一定要在操作隊(duì)列添加了操作后再設(shè)置锅铅。即酪呻,先向operation queue中添加operation,再調(diào)用[operationQueue waitUntilAllOperationsAreFinished]
盐须。
NSBlockOperation *blkop = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"執(zhí)行操作 %@",[NSThread currentThread]);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:blkop];
// waitUntilAllOperationsAreFinished就像GCD的barrier一樣起到隔離作用
// waitUntilAllOperationsAreFinished必須要在操作添加到隊(duì)列后設(shè)置
// waitUntilAllOperationsAreFinished必須要在NSLog(@"finish");之前設(shè)置
waitUntilAllOperationsAreFinished
[queue waitUntilAllOperationsAreFinished];
NSLog(@"finish");
(5.6)operationCount
operationCount玩荠,顧名思義,就是指在隊(duì)列中當(dāng)前操作而數(shù)量。因?yàn)橹挥嘘?duì)列中的操作被執(zhí)行完成后阶冈,這個(gè)operationCount值才會(huì)改變闷尿,所以operationCount值包括當(dāng)前正在執(zhí)行的operation和還沒(méi)有被執(zhí)行的操、operation女坑。我們獲取到的operationCount只是當(dāng)前隊(duì)列里操作數(shù)量的瞬間值填具。當(dāng)我們用到這個(gè)operationCount的時(shí)候,很有可能隊(duì)列中實(shí)際的operationCount已經(jīng)發(fā)生了改變(因?yàn)椴僮饔锌赡苁钱惒綀?zhí)行的)堂飞。所以灌旧,蘋果不建議我們?cè)陂_(kāi)發(fā)中使用這個(gè)值(比如根據(jù)這個(gè)值遍歷數(shù)組中的操作或者進(jìn)行一些其他精確的計(jì)算),很有可能引起錯(cuò)誤甚至程序崩潰(比如绰筛,數(shù)組越界)。
如果你非要使用的話描融,建議你使用KVO的方式監(jiān)聽(tīng)隊(duì)列的operationCount的變化铝噩。杜絕直接使用operationCount。好吧窿克,這句話是蘋果爸爸說(shuō)的:You may monitor changes to the value of this property using Key-value observing. Configure an observer to monitor the operationCount
key path of the operation queue.
例如:工作中曾經(jīng)寫過(guò)的一段代碼骏庸,就有可能引起崩潰:
(自定義了一個(gè)NSOperationQueue,并且覆寫了addOperation:方法年叮,當(dāng)初寫這段代碼的初衷是當(dāng)操作很多的時(shí)候具被,舍棄多余的操作,只處理maxTaskCount個(gè)操作)
-(void)addOperation:(NSOperation *)op{
self.suspended = YES;
NSInteger cancelCount = self.operationCount - self.maxTaskCount;
if (self.operationCount > self.maxTaskCount) {
for (int i = 0; i < cancelCount; i++) {
NSOperation *op = [self.operations objectAtIndex:i];
if([op isKindOfClass:[NSOperation class]]){
[op cancel];
}else{
break;
}
}
}
self.suspended = NO;
[super addOperation:op];
}
文/VV木公子(簡(jiǎn)書作者)
PS:如非特別說(shuō)明只损,所有文章均為原創(chuàng)作品一姿,著作權(quán)歸作者所有,轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)跃惫,并注明出處叮叹,所有打賞均歸本人所有!
如果您是iOS開(kāi)發(fā)者爆存,或者對(duì)本篇文章感興趣蛉顽,請(qǐng)關(guān)注本人,后續(xù)會(huì)更新更多相關(guān)文章先较!敬請(qǐng)期待携冤!
參考文章
多線程編程2-NSOperation
多線程編程3 - NSOperationQueue
并發(fā)編程:API 及挑戰(zhàn)