iOS-NSOperation

NSOperation是一个抽象类,包含了代码和数据的简单task。

定义

NSOperation可以使用系统定义好的以下子类来执行task.

NSInvocationOperation
NSBlockOperation

NSOperation内部包含了安全逻辑,可以让你无需关注于其他系统对象的交互是否正确。一个NSOperation对象是单次有效,只能执行一次,不能重复执行。

启动

一个NSOperation队列直接在一个新创建的线程上运行,或者使用libdispatch库(GCD)。

如果不想使用NSOperation队列,也可以调用NSOperation的start方法直接运行。注意,开启一个处于未ready状态的NSOperation会触发异常,可以通过ready属性判断。

举例:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"I'm Block Operation..");
    }];
    if(blockOp.ready)
        [blockOp start];

    NSInvocationOperation *invocationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOp) object:nil];
    if(invocationOp.ready)
        [invocationOp start];
}

- (void)invocationOp
{
    NSLog(@"I'm Invocation Operation..");
}

运行结果:

I'm Block Operation..
I'm Invocation Operation..

依赖

Dependencied(依赖)是一种可以让Operations按照特定的顺序运行的便利方式。可以通过以下方法管理依赖。

addDependency:
removeDependency:

一个Operation对象只有当它所有的依赖都完成运行以后,才会处于ready状态。

举例:

NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"I'm Block Operation..");
}];
NSInvocationOperation *invocationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOp) object:nil];
[blockOp addDependency:invocationOp];
[blockOp start];
if(invocationOp.ready)
    [invocationOp start];

在blockOp的依赖未完成前,调用start, 运行结果:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', 
reason: '*** -[__NSOperationInternal _start:]: receiver is not yet ready to execute'

NSOperation不会判断它的依赖Operation是否成功完成(取消一个Operation也将标记其为完成),如果需要,你必须在Operation对象中加入额外的异常跟踪能力。

static NSInvocationOperation *invocationOp;
- (void)viewDidLoad {
    [super viewDidLoad];

    NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
        [blockOp cancel];
          NSLog(@"I'm Block Operation..");
    }];
    invocationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOp) object:nil];
    [blockOp addDependency:invocationOp];
    [invocationOp cancel];
    [invocationOp start];
    while(!blockOp.ready);
    NSLog(@"%@", (invocationOp.cancelled ? @"Cancel" : @"Not Cancel.." ));
    [blockOp start];
}

- (void)invocationOp
{
    NSLog(@"I'm Invocation Operation..");
}

运行结果为:

Cancel
I'm Block Operation..

KVO属性

NSOperation的一些属性支持KVC和KVO。有必要的时候,可以监听这些属性值的变化。

下面是支持的属性值:

  • isCancelled 只读
  • isAsynchronous 只读
  • isExecuting 只读
  • isFinished 只读
  • isReady 只读
  • dependencies 只读
  • queuePriority 读写
  • completionBlock 读写

注意,请不要将上述属性变化与你应用的界面绑定在一起,因为与界面相关的代码必须在主线程运行,而一个Operation可能在其他线程中运行,与Operation相关的KVO的Notification可能在任何线程中被触发。

如果自定义或者重写了NSOperation的上述属性必须包含KVC和KVO。如果新增了其他属性,也建议给那些属性加上KVC和KVO。

多核处理

NSOperation本身对多核的情况进行了处理,所以可以在不同线程中调用NSOperation对象,而无需创建锁。因为NSOperation经常是被一个线程创建并监听,而在另一个线程上运行,所以NSOperation本身对这种情况进行了保护。

如果,你继承了NSOperation,你必须确认任何重写或者新增的方法保持线程安全。

同步和异步

NSOperation默认是同步的,不会创建新的线程。

而如果调用一个异步Operation的start方法,该方法可能还没执行完就可以返回,从而不阻塞当前线程。异步Operation可以直接启动一个新的线程。定义一个异步的Operation需要做一些工作,例如跟踪Operation的状态变化,但是它可以不block当前线程。创建一个异步Operation参考下面的章节。

注意:如果你往一个Operation队列中加入一个Operation,队列将忽略asynchronous属性。

创建NSOperation子类

NSOperation类提供了基础的跟踪运行状态的逻辑,但是子类仍然需要做一些工作。

重写方法

对于同步的Operation,只需要重写一个方法:

main

举例:

@interface MyNonConcurrentOperation : NSOperation
    @property id (strong) myData;
    -(id)initWithData:(id)data;
@end

@implementation MyNonConcurrentOperation
- (id)initWithData:(id)data {
       if (self = [super init])
       myData = data;
       return self;
}

-(void)main {
    @try {
        // Do some work on myData and report the results.
    }@catch(...) {
        // Do not rethrow exceptions.
    }
}
@end

在main方法中,编写需要执行的task逻辑。你可能还需要创建init方法和一些getter,setter方法,注意线程安全。


对于异步的Operation,至少需要重写以下方法和属性:

start
main(Option,用于处理事件)
asynchronous
executing
finished

对于异步的Operation,start方法可以在内部以一种异步的方式开始一个Operation,无论是创建一个新的线程还是调用异步方法。start方法同时需要更新运行的状态executing属性,并发出KVO通知,KVO的Key为executing。executing属性必须线程安全。

注意:start方法不要调用super,且必须在启动task前检查是否已被cancel。

一旦task完成或者取消,创建的Operation必须更新executing和finished属性并发出KVO通知。KVO的Key分别为isExecuting和isFinished。

注意:取消的时候,也发出isFinished通知,原因是Operation队列根据这个来移除Operation。

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@interface CustomOperation ()

@property (nonatomic, assign) BOOL myExecuting;
@property (nonatomic, assign) BOOL myFinished;

@end

@implementation CustomOperation

- (id)init
{
if(self = [super init]){
NSLog(@"Custom Init..");
_myExecuting = NO;
_myFinished = NO;
}
return self;
}

- (void)start
{
if(self.isCancelled){
NSLog(@"Cancel..");
[self willChangeValueForKey:@"isFinished"];
_myFinished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
NSLog(@"Start..");
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
[self willChangeValueForKey:@"isExecuting"];
_myExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
}

- (void)main
{
int index = 0;
while(index++ < 5){
NSLog(@"%d", index);
[NSThread sleepForTimeInterval:1.0f];
}
NSLog(@"Finish..");
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_myExecuting = NO;
_myFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}

- (BOOL)isAsynchronous
{
return YES;
}

- (BOOL)isExecuting
{
return _myExecuting;
}

- (BOOL)isFinished
{
return _myFinished;
}

@end

维护状态

状态列表如下:






















Key PathDescription
isReadyisReady Key Path让客户端知道Operation准备运行,此时ready属性必须为YES。一般情况下,无需手动管理,除非你的Operation依赖一些外部条件才能开始运行。
isExectingisReady Key Path让客户端知道Operation正在运行运行,此时executing属性必须为YES。
isFinishedisReady Key Path让客户端知道Operation完成或者取消,此时finished属性必须为YES。
isCancelled isCancelled Key Path让客户端知道Operation请求取消,对于取消不是强制的,但是建议支持取消功能,注意:不需要发送KVO通知

响应Cancel命令

一旦将一个Operation加入到队列中,Operation将由队列控制,此时,可以调用以下方法进行取消。

cancelAllOperations

取消一个Operation并不会强制其停止运行,如有必要,你的代码可以显式地检查cancelled的值,并强行中断。

NSOperation的默认实现,在start前面调用cancel的话,将不会启动task。

自定义NSOperation子类时,需要周期性检查cancelled的值,一旦为YES,应该尽可能快得清除空间并退出。

举例:

1
2
3
4
5
6
7
8
9
10
- (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.
}
}

控制顺序

控制NSOperatio在Queue中的几种方式:

依赖控制

参考上文

设置Queue Priority

调用NSOperation的方法:

setQueuePriority:

可以设置在队列中的优先级。注意,优先级只在同一Queue中有效,且只在同时处于ready状态的几个Operation中比较。

设置Thread Priority

NSOperation的属性:

threadPriority

可以设置NSOperation所处线程的优先级。如果在添加到queue之前设置,将在start前生效,如果不是,新的优先级将只在main函数中生效。

设置Completion Block

Completion Block将会在Operation完成后被调用。