NSOperation是對(duì)GCD的高級(jí)封裝。相對(duì)于GCD,使用NSOperation更加符合面對(duì)對(duì)象的編程習(xí)慣,更重要的是,NSOperation提供了更多的特性方便開發(fā)者監(jiān)控和管理要并發(fā)執(zhí)行的任務(wù):
- 可以簡(jiǎn)便地設(shè)置任務(wù)之間的依賴關(guān)系
- 可以對(duì)取消正在執(zhí)行的任務(wù)
- 可以只用KVO的方式來(lái)監(jiān)控任務(wù)的當(dāng)前狀態(tài)
- 可以針對(duì)任務(wù)設(shè)置優(yōu)先級(jí)
- 可以為任務(wù)設(shè)置一個(gè)完成后執(zhí)行的competitionBlock
創(chuàng)建NSOperation
NSOperation有兩個(gè)具體的子類,NSInvocationOperation和NSBlockOperation,創(chuàng)建方法如下(官方Demo):
@implementation MyCustomClass
- (NSOperation*)taskWithData:(id)data {
NSInvocationOperation* theOp = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(myTaskMethod:) object:data];
return theOp;
}
// This is the method that does the actual work of the task.
- (void)myTaskMethod:(id)data {
// Perform the task.
}
@end
NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{
NSLog(@"Beginning operation.\n");
// Do some work.
}];
NSOperation的同步和異步執(zhí)行
NSOperation對(duì)象調(diào)用start方法執(zhí)行,默認(rèn)是在調(diào)用的線程同步執(zhí)行的。當(dāng)一個(gè)線程調(diào)用start方法時(shí),operation會(huì)立即在該線程中執(zhí)行。當(dāng)operation結(jié)束后線程才會(huì)繼續(xù)執(zhí)行之后的代碼。
對(duì)一個(gè)異步的operation來(lái)說(shuō),當(dāng)調(diào)用其start方法時(shí),該operation可能會(huì)開啟一個(gè)新的線程執(zhí)行,調(diào)用一個(gè)異步方法或者將block提交到一個(gè)dispatch_queue,總之調(diào)用的線程會(huì)繼續(xù)執(zhí)行余下的代碼而不會(huì)等待operation完成。
NSOperationQueue
大多數(shù)情況下并不需要?jiǎng)?chuàng)建一個(gè)異步的operation,這需要開發(fā)者做很多額外的工作。更普遍的做法是將NSOperation放進(jìn)NSOperationQueue中去執(zhí)行。當(dāng)你將一個(gè)operation加進(jìn)operation queue中,那么無(wú)論該operation的asynchronous 屬性是什么,調(diào)用者都會(huì)在一個(gè)另外一個(gè)的線程中去執(zhí)行start方法。所以當(dāng)使用NSOperationQueue來(lái)管理執(zhí)行的話,完全沒有必要?jiǎng)?chuàng)建異步的operation。
當(dāng)operation添加進(jìn)operation queue后,不需要調(diào)用任何方法,operation queue 中的operation就會(huì)開始執(zhí)行。
設(shè)置依賴
用法簡(jiǎn)單,直接上一段代碼:
- (void)opDependency{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *calculateOp1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start do some calulation 1");
[NSThread sleepForTimeInterval:3];
NSLog(@"end cal 1, do some update");
}];
[queue addOperation:calculateOp1];
NSOperation *calculateOp2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start do some calulation 2");
[NSThread sleepForTimeInterval:2];
NSLog(@"end cal 2, do some update");
}];
[queue addOperation:calculateOp2];
NSOperation *doAfterPreOpDone = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"do something which depend on op1 and op2");
}];
[doAfterPreOpDone addDependency:calculateOp1];
[doAfterPreOpDone addDependency:calculateOp2];
[queue addOperation:doAfterPreOpDone];
}
運(yùn)行結(jié)果為:
2017-04-20 15:53:06.062 ConcurrentProgram[98523:43310892] start do some calulation 1
2017-04-20 15:53:06.062 ConcurrentProgram[98523:43310890] start do some calulation 2
2017-04-20 15:53:08.134 ConcurrentProgram[98523:43310890] end cal 2, do some update
2017-04-20 15:53:09.137 ConcurrentProgram[98523:43310892] end cal 1, do some update
2017-04-20 15:53:09.137 ConcurrentProgram[98523:43310890] do something which depend on op1 and op2
取消NSOperation
將上面的代碼稍微改動(dòng)一下,測(cè)試一下NSOperation的cancel功能。
- (void)opDependency{
__block long i = 0;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *calculateOp1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start do some calulation 1");
while (true) {
i++;
}
}];
[queue addOperation:calculateOp1];
NSOperation *calculateOp2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"start do some calulation 2");
[NSThread sleepForTimeInterval:2];
NSLog(@"end cal 2, do some update");
[calculateOp1 cancel];
}];
[queue addOperation:calculateOp2];
NSOperation *doAfterPreOpDone = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"do something which depend on op1 and op2");
NSLog(@"i is %ld",i);
}];
[doAfterPreOpDone addDependency:calculateOp1];
[doAfterPreOpDone addDependency:calculateOp2];
[queue addOperation:doAfterPreOpDone];
}
在calculateOp1中設(shè)置了一個(gè)無(wú)限循環(huán)的block,然后我們希望在calculateOp2中去取消掉calculateOp1。預(yù)期結(jié)果是在calculateOp2中的block執(zhí)行完以后會(huì)立即執(zhí)行doAfterPreOpDone中的任務(wù)。(因?yàn)榱硪粋€(gè)依賴被取消了)
結(jié)果如下:
2017-04-20 16:38:01.658 ConcurrentProgram[542:43544549] start do some calulation 1
2017-04-20 16:38:01.658 ConcurrentProgram[542:43544548] start do some calulation 2
2017-04-20 16:38:03.730 ConcurrentProgram[542:43544548] end cal 2, do some update
發(fā)現(xiàn)calculateOp1并沒有取消而doAfterPreOpDone一直不會(huì)執(zhí)行。
去查閱官方的API文檔,發(fā)現(xiàn)有這樣的一段話:
Canceling an operation does not immediately force it to stop what it is doing. Although respecting the value in the cancelled property is expected of all operations, your code must explicitly check the value in this property and abort as needed. The default implementation of NSOperation includes checks for cancellation. For example, if you cancel an operation before its start method is called, the start method exits without starting the task.
也就是說(shuō)調(diào)用cancel方法并不會(huì)立即停止operation中的動(dòng)作,只是將cancelled屬性設(shè)置為YES,真正的取消動(dòng)作是需要開發(fā)者自己實(shí)現(xiàn)的。一般來(lái)說(shuō),要繼承NSOperation類,在start方法中不停地檢查isCancel屬性。在默認(rèn)的NSOeration的實(shí)現(xiàn)中也包括了cancel屬性的檢查,如果你在operation的start方法之前cancel了,那么該operation就不會(huì)執(zhí)行了。
官方提供了一個(gè)示例(將main方法改為start方法效果一樣)
- (void)main {
@try {
BOOL isDone = NO;
while (![self isCancelled] && !isDone) {
// Do some work and set isDone to YES when finished
}
}
@catch(...) {
// Do not rethrow exceptions.
}
}
那么之前的代碼相當(dāng)于:
- (void)main {
@try {
BOOL isDone = NO;
while (![self isCancelled] && !isDone) {
NSLog(@"start do some calulation 1");
while (true) {
i++;
}
}
}
@catch(...) {
// Do not rethrow exceptions.
}
}
檢查cancel屬性的代碼在打印之前,在打印之后取消該block就沒有效果了。