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 | @interface CustomOperation () |
维护状态
状态列表如下:
Key Path | Description |
---|---|
isReady | isReady Key Path让客户端知道Operation准备运行,此时ready属性必须为YES。一般情况下,无需手动管理,除非你的Operation依赖一些外部条件才能开始运行。 |
isExecting | isReady Key Path让客户端知道Operation正在运行运行,此时executing属性必须为YES。 |
isFinished | isReady 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 | - (void)main { |
控制顺序
控制NSOperatio在Queue中的几种方式:
依赖控制
参考上文
设置Queue Priority
调用NSOperation的方法:
setQueuePriority:
可以设置在队列中的优先级。注意,优先级只在同一Queue中有效,且只在同时处于ready状态的几个Operation中比较。
设置Thread Priority
NSOperation的属性:
threadPriority
可以设置NSOperation所处线程的优先级。如果在添加到queue之前设置,将在start前生效,如果不是,新的优先级将只在main函数中生效。
设置Completion Block
Completion Block将会在Operation完成后被调用。