NSOperation表示了一个独立的计算单元。作为一个抽象类,它给了它的子类一个十分有用而且线程安全的方式来建立状态、优先级、依赖性和取消等的模型。你可以使用系统提供的NSBlockOperation
和NSInvocationOperation
方法来创建一个operation,也可以创建一个继承NSOperation
抽象类的operation。
异步vs同步Operations
Operations分为同步和异步。创建的operations默认是一个同步的operation。如果你调用start
方法启动一个operation,它会在当前线程执行,并且阻塞当前线程直到这个operation结束。另外,你可以把operations放入operation queue中。放入operation queue中的operations会另起一个线程调用start
方法,因此会异步执行。当然你可以创建一个异步的operation,这需要重写很多方法,比较麻烦,建议是直接放入operation queue中。
NSOperation的状态
NSOperation包含了一个状态机来描述每一个操作的执行。
- isReady 已经准备好执行
- isExecuting 正在执行
- isFinished 执行成功或取消
这三种状态是相互独立的,同时只能是一个状态属性返回YES。这些状态由keypath的KVO通知决定。
NSOperation依赖性
如果某些operation需要按照一定的次序执行。则可以通过addDependency
为相应的队列添加依赖。但是在添加依赖的时候要注意依赖循环,从而导致死循环。
NSOperation优先级
你可以通过operation的queuePriority
来设置优先级,从而加快或者延迟queue中的operation的执行。默认的queuePriority
是NSOperationQueuePriorityNormal
。
- NSOperationQueuePriorityVeryLow
- NSOperationQueuePriorityLow
- NSOperationQueuePriorityNormal
- NSOperationQueuePriorityHigh
- NSOperationQueuePriorityVeryHigh
但是priority不能与dependency一起使用。添加了dependency的operation一定严格按照dependency的顺序执行。
同时你可以通过operation的qualityOfService
来设置系统资源对operation的保障。高保障的operation的优先级大于低保障的operation的优先级。默认的保障等级是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);
}
执行结果:
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
当blkOperation被加入到queue时,这个blkOperation才会被执行。加入queue中的operations是并发执行的。所以invoOperation会同时和blkOperation一起执行。不过由于queue遵循FIFO原则,所以一般会blkOperation先于invoOperation执行。由于blkOperation的cancel
在1s后执行,此时由于blkOperation已经在执行,所以无法取消。waitUntilFinished
必须是在加入queue后才能执行,不然会死锁。waitUntilFinished
后面的代码会在当前operation执行完之后才会执行。completionBlock
表示这个opertion执行完之后做的处理。
接下来介绍一下自定义并发的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
方法是必须实现的。main
方法不是必须实现的,但是为了结构清晰,一般会把要做的任务放在main函数中。
start
方法是operation的执行起点。但是当这个operation被cancel掉时,也需要设置operation的状态为finished。对于并发的operation,就是另外启动一个线程来执行main方法,同时isConcurrent
方法一直返回YES。
另外我们得自己维护operation的状态,同时触发相应的KVO通知。
NSOperation VS GCD
NSOperation是对GCD的封装。使用NSOperation也就是在使用GCD。由于NSOperation是更高级的API,因此它拥有更多的功能。
- 依赖性
- 可观察状态
- 停止,取消,启动
- 控制并发
虽然苹果建议使用更高级的API,但是如果GCD能够满足要求的话,还是建议用GCD,因为它更轻量。如果需要按照一定顺序执行或者其他高要求的话,可以使用NSOpertation。
参考
Operation
Choosing Between NSOperation and Grand Central Dispatch
iOS 并发编程之 Operation Queues
iOS多线程之NSOperation和NSOperationQueue