NSOperation

NSOperation表示了一个独立的计算单元。作为一个抽象类,它给了它的子类一个十分有用而且线程安全的方式来建立状态、优先级、依赖性和取消等的模型。你可以使用系统提供的NSBlockOperationNSInvocationOperation方法来创建一个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的执行。默认的queuePriorityNSOperationQueuePriorityNormal

  • 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

Author: MrHook
Link: https://bigjar.github.io/2018/01/29/NSOperation/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.