NSOperation是對GCD的高級封裝。相對于GCD,使用NSOperation更加符合面對對象的編程習(xí)慣,更重要的是,NSOperation提供了更多的特性方便開發(fā)者監(jiān)控和管理要并發(fā)執(zhí)行的任務(wù):
- 可以簡便地設(shè)置任務(wù)之間的依賴關(guān)系
- 可以對取消正在執(zhí)行的任務(wù)
- 可以只用KVO的方式來監(jiān)控任務(wù)的當(dāng)前狀態(tài)
- 可以針對任務(wù)設(shè)置優(yōu)先級
- 可以為任務(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對象調(diào)用start方法執(zhí)行犀忱,默認(rèn)是在調(diào)用的線程同步執(zhí)行的募谎。當(dāng)一個(gè)線程調(diào)用start方法時(shí),operation會立即在該線程中執(zhí)行阴汇。當(dāng)operation結(jié)束后線程才會繼續(xù)執(zhí)行之后的代碼数冬。
對一個(gè)異步的operation來說,當(dāng)調(diào)用其start方法時(shí)搀庶,該operation可能會開啟一個(gè)新的線程執(zhí)行拐纱,調(diào)用一個(gè)異步方法或者將block提交到一個(gè)dispatch_queue,總之調(diào)用的線程會繼續(xù)執(zhí)行余下的代碼而不會等待operation完成哥倔。
NSOperationQueue
大多數(shù)情況下并不需要?jiǎng)?chuàng)建一個(gè)異步的operation秸架,這需要開發(fā)者做很多額外的工作。更普遍的做法是將NSOperation放進(jìn)NSOperationQueue中去執(zhí)行咆蒿。當(dāng)你將一個(gè)operation加進(jìn)operation queue中东抹,那么無論該operation的asynchronous
屬性是什么,調(diào)用者都會在一個(gè)另外一個(gè)的線程中去執(zhí)行start方法沃测。所以當(dāng)使用NSOperationQueue來管理執(zhí)行的話缭黔,完全沒有必要?jiǎng)?chuàng)建異步的operation。
當(dāng)operation添加進(jìn)operation queue后蒂破,不需要調(diào)用任何方法馏谨,operation queue 中的operation就會開始執(zhí)行。
設(shè)置依賴
用法簡單附迷,直接上一段代碼:
- (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
將上面的代碼稍微改動一下惧互,測試一下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è)無限循環(huán)的block挟秤,然后我們希望在calculateOp2中去取消掉calculateOp1。預(yù)期結(jié)果是在calculateOp2中的block執(zhí)行完以后會立即執(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一直不會執(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.
也就是說調(diào)用cancel方法并不會立即停止operation中的動作截珍,只是將cancelled屬性設(shè)置為YES,真正的取消動作是需要開發(fā)者自己實(shí)現(xiàn)的攀甚。一般來說箩朴,要繼承NSOperation類,在start方法中不停地檢查isCancel屬性秋度。在默認(rèn)的NSOeration的實(shí)現(xiàn)中也包括了cancel屬性的檢查炸庞,如果你在operation的start方法之前cancel了,那么該operation就不會執(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就沒有效果了。