NSOperation表示了一個獨立的計算單元。作為一個抽象類,它給了它的子類一個十分有用而且線程安全的方式來建立狀態(tài)、優(yōu)先級、依賴性和取消等的模型。你可以使用系統(tǒng)提供的NSBlockOperation和NSInvocationOperation方法來創(chuàng)建一個operation,也可以創(chuàng)建一個繼承NSOperation抽象類的operation。
異步vs同步Operations
Operations分為同步和異步。創(chuàng)建的operations默認(rèn)是一個同步的operation。如果你調(diào)用start方法啟動一個operation,它會在當(dāng)前線程執(zhí)行,并且阻塞當(dāng)前線程直到這個operation結(jié)束。另外,你可以把operations放入operation queue中。放入operation queue中的operations會另起一個線程調(diào)用start方法,因此會異步執(zhí)行。當(dāng)然你可以創(chuàng)建一個異步的operation,這需要重寫很多方法,比較麻煩,建議是直接放入operation queue中。
NSOperation的狀態(tài)
NSOperation包含了一個狀態(tài)機來描述每一個操作的執(zhí)行。
- isReady 已經(jīng)準(zhǔn)備好執(zhí)行
- isExecuting 正在執(zhí)行
- isFinished 執(zhí)行成功或取消
這三種狀態(tài)是相互獨立的,同時只能是一個狀態(tài)屬性返回YES。這些狀態(tài)由keypath的KVO通知決定。
NSOperation依賴性
如果某些operation需要按照一定的次序執(zhí)行。則可以通過addDependency為相應(yīng)的隊列添加依賴。但是在添加依賴的時候要注意依賴循環(huán),從而導(dǎo)致死循環(huán)。
NSOperation優(yōu)先級
你可以通過operation的queuePriority來設(shè)置優(yōu)先級,從而加快或者延遲queue中的operation的執(zhí)行。默認(rèn)的queuePriority是NSOperationQueuePriorityNormal。
- NSOperationQueuePriorityVeryLow
- NSOperationQueuePriorityLow
- NSOperationQueuePriorityNormal
- NSOperationQueuePriorityHigh
- NSOperationQueuePriorityVeryHigh
但是priority不能與dependency一起使用。添加了dependency的operation一定嚴(yán)格按照dependency的順序執(zhí)行。
同時你可以通過operation的qualityOfService來設(shè)置系統(tǒng)資源對operation的保障。高保障的operation的優(yōu)先級大于低保障的operation的優(yōu)先級。默認(rèn)的保障等級是NSQualityOfServiceBackground。
- NSQualityOfServiceUserInteractive
- NSQualityOfServiceUserInitiated
- NSQualityOfServiceUtility
- NSQualityOfServiceBackground
NSOperation用法
首先介紹以下NSBlockOperation和NSInvocationOperation。代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *blkOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blk start");
[NSThread sleepForTimeInterval:3];
NSLog(@"blk finished");
}];
blkOperation.queuePriority = NSOperationQueuePriorityHigh;
blkOperation.qualityOfService = NSQualityOfServiceUserInitiated;
NSMutableArray *arr = [NSMutableArray arrayWithArray:@[@1,@2]];
NSOperation *invoOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomethingWithArr:) object:arr];
NSLog(@"question");
[queue addOperation:blkOperation];
blkOperation.completionBlock = ^{
NSLog(@"haha");
};
[NSThread sleepForTimeInterval:1];
[blkOperation cancel];
[queue addOperation:invoOperation];
[blkOperation waitUntilFinished];
NSLog(@"answer");
}
- (void)doSomethingWithArr:(NSMutableArray *)arr {
NSLog(@"arr is %@ in invo",arr);
}
執(zhí)行結(jié)果:
2018-01-05 09:13:31.205077+0800 NSOperationDemo[25280:6903063] question
2018-01-05 09:13:31.205314+0800 NSOperationDemo[25280:6903118] blk start
2018-01-05 09:13:32.206698+0800 NSOperationDemo[25280:6903116] arr is (
1,
2
) in invo
2018-01-05 09:13:34.209701+0800 NSOperationDemo[25280:6903118] blk finished
2018-01-05 09:13:34.209970+0800 NSOperationDemo[25280:6903063] answer
2018-01-05 09:13:34.209981+0800 NSOperationDemo[25280:6903116] haha
當(dāng)blkOperation被加入到queue時,這個blkOperation才會被執(zhí)行。加入queue中的operations是并發(fā)執(zhí)行的。所以invoOperation會同時和blkOperation一起執(zhí)行。不過由于queue遵循FIFO原則,所以一般會blkOperation先于invoOperation執(zhí)行。由于blkOperation的cancel在1s后執(zhí)行,此時由于blkOperation已經(jīng)在執(zhí)行,所以無法取消。waitUntilFinished必須是在加入queue后才能執(zhí)行,不然會死鎖。waitUntilFinished后面的代碼會在當(dāng)前operation執(zhí)行完之后才會執(zhí)行。completionBlock表示這個opertion執(zhí)行完之后做的處理。
接下來介紹一下自定義并發(fā)的operation。代碼如下:
@interface AWOperation() {
BOOL executing;
BOOL finished;
}
@end
@implementation AWOperation
- (instancetype)init {
if (self = [super init]) {
executing = NO;
finished = NO;
}
return self;
}
- (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"];
}
- (void)main {
@try {
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
[self didChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
} @catch (NSException *exception) {
NSLog(@"Exception: %@", exception);
}
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
- (BOOL)isConcurrent {
return YES;
}
對于自定義oepration,start, isExecuting, isFinished, isConcurrent方法是必須實現(xiàn)的。main方法不是必須實現(xiàn)的,但是為了結(jié)構(gòu)清晰,一般會把要做的任務(wù)放在main函數(shù)中。
start方法是operation的執(zhí)行起點。但是當(dāng)這個operation被cancel掉時,也需要設(shè)置operation的狀態(tài)為finished。對于并發(fā)的operation,就是另外啟動一個線程來執(zhí)行main方法,同時isConcurrent方法一直返回YES。
另外我們得自己維護operation的狀態(tài),同時觸發(fā)相應(yīng)的KVO通知。
NSOperation VS GCD
NSOperation是對GCD的封裝。使用NSOperation也就是在使用GCD。由于NSOperation是更高級的API,因此它擁有更多的功能。
- 依賴性
- 可觀察狀態(tài)
- 停止,取消,啟動
- 控制并發(fā)
雖然蘋果建議使用更高級的API,但是如果GCD能夠滿足要求的話,還是建議用GCD,因為它更輕量。如果需要按照一定順序執(zhí)行或者其他高要求的話,可以使用NSOpertation。
參考
Operation
Choosing Between NSOperation and Grand Central Dispatch
iOS 并發(fā)編程之 Operation Queues
iOS多線程之NSOperation和NSOperationQueue