NSOperation:
即操作对象,是一个抽象类,用于封装和单个任务关联的代码和数据。因为它是抽象的,所以不直接使用这个类,而是使用子类(NSInvocationOperation或NSBlockOperation)或使用自定义的子类之一来执行实际任务。
尽管NSOperation是抽象类,但其基本实现重要的逻辑来协调我们任务并安全执行,这种内置的逻辑使得我们可以专注于我们任务的实现,而不是和其他事务逻辑耦合。
NSOperation是一次性对象,即它只会执行一次任务,不能再使用它来执行其他任务。我们通常将操作对象添加到操作队列(NSOperationQueue类的实例)来同步或异步的执行操作,除了在操作队列上使用,我们还可以开子线程运行他们或使用结合GCD来执行操作,此时这些操作任务会在当前线程上执行。
如果将NSOperation对象添加到NSOperationQueue队列上,操作对象们会被操作队列管理开启操作,如果单独使用操作队列,则需要直接调用其start
方法来开启任务。手动执行操作会给代码带来更多的负担,因为启动未处于isReady
状态的操作会触发异常。
NSOperationQueue:
NSOperationQueue类是管理调用一组NSOperation对象的类。当操作对象加入到队列,该操作直到被明确cancelled
或finished
前都会被保留在队列中。操作队列中的操作本身根据优先级和其他操作项的对象依赖性进行组织,并相应的执行。应用程序可能会创建多个操作队列并将操作提交给它们中的任何一个。
即使这些操作对象位于不同的操作队列中,操作间的依赖关系也是有效的。操作对象在其所有依赖操作完成执行之前不会进入isReady
就绪状态。对于isReady
状态的操作对象,最高优先级的操作的对象将会被优先执行。
对于已加入到操作队列里的操作对象,我们无法将其移除,操作队列会保持在队列中,直到其完成其任务,当然,这里的完成是一种状态,并不一定意味着真正的完成了其任务,因为操作对象在未被执行前,是可以取消的。取消操作对象依旧被保留在队列中,但是会通知对象尽快终止其任务。对于当前正在执行的操作,这意味着操作对象的工作代码必须检查取消状态,停止正在执行的操作,并将自己标记为finished
已完成状态。对于’isReady’状态但是尚未执行的操作,队列仍必须调用该操作对象的start
方法,以便它可以处理取消事件并将自己标记为finished
已完成状态。
取消操作会导致操作忽略一切和它具有依赖关系。这意味着和其有依赖关系的操作可能更早的被执行。
和NSOperation一样,操作队列通常会在当前线程。另外,操作队列会使用GCD来启动其操作对象的执行,因此,操作对象总是在单独的线程上执行,而不管它们是否被指定为异步或同步操作。
上面是官方给出的解释,总体而言,NSOperation是操作对象类,NSOperationQueue是操作对象的调度者。
NSOperation类是一个抽象类,该类并没赋予其完成任务的能力,我们可以使用它的两个子类NSBlockOperation
和NSInvocationOperation
,前者是通过block的形式完成任务,后者是则使用方法选择器实现。另外,我们还可以自定义自己的操作类。
NSBlockOperation用于管理一个或多个block任务的并发执行,我们可以使用该对象实现一次执行多个block任务,而无需为每个block任务创建单独的操作对象。当执行多个block任务时,只有当所有的block完成时才认为操作本身已完成。
注:添加到block的任务是以默认优先级调度到队列的,并且block任务内部不可以在去更改执行环境。
-(void)useBlockOperation{
// 创建操作对象
NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
// 单独使用操作对象时需要手动开启任务
// 启动
[bop start];
}
当我们在主线程中测试时:
[self useBlockOperation];
当我们在某线程中测试时:
[NSThread detachNewThreadSelector:@selector(useBlockOperation) toTarget:self withObject:nil];
上面两个测试可知:操作对象会在给定的线程中执行任务。
NSBlockOperation对象可以添加更多的block任务,并在主线程中调用
-(void)useBlockOperation{
// 创建操作对象
NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"origin:%@",[NSThread currentThread]);
}];
for (int i=0; i<10; i++) {
// 添加更多的额外任务
[bop addExecutionBlock:^{
NSLog(@"%@:%@",[NSString stringWithFormat:@"%i",i],[NSThread currentThread]);
}];
NSLog(@"操作任务是否完成:%@",bop.isFinished?@"YES":@"NO");
}
// 启动
[bop start];
NSLog(@"操作任务是否完成:%@",bop.isFinished?@"YES":@"NO");
}
我们发现,block中的操作任务是并发执行的,系统会在开启一个或多个线程执行这些任务,可以看到任务是在1和3号线程执行的,其中1号线程是主线程,因为我们是在主线程中调用操作对象的。如果我们在子线程中调用,则不会出现主线程,而会继续创建子线程去执行任务。另外,我们发现,只有当所有的block完成时,操作本身才会被标记为已完成状态。
NSInvocationOperation对象用于指定调用者的任务。我们可以通过某个对象开启一个操作,并指定对象调用选择器执行任务。
[NSThread detachNewThreadSelector:@selector(useInvocationOperation) toTarget:self withObject:nil];
// invocation操作对象
-(void)useInvocationOperation{
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation:) object:@{@"name":@"NSInvocationOperation"}];
[op start];
}
// 任务
-(void)invocationOperation:(NSDictionary*)dic{
NSLog(@"%@\n%@",[NSThread currentThread],dic);
}
我们看到NSInvocationOperation在新开启的线程被执行。
对于NSOperation类,我们发现,NSOperation的对象执行任务时,除了BlockOperation中的addExecutionBlock
开启了新的线程,似乎和我们普通对象调用方法没什么不同,都是在当前线程执行任务,想要实现多线程编程,都需要借助GCD或NSTread来开启子线程,那这个操作类是否显得非常鸡肋呢?当然不是,其实NSOperation是需要结合NSOperationQueue才能发挥其作用的,NSOperationQueue是基于GCD的封装对象,用来便捷的完成NSOperation对象执行操作事务的调度工作。
NSOperationQueue 可以调度 NSOperation 对象,队列中的操作对象都是并发执行。
-(void)useOperationQueue{
NSLog(@"queue:%@",[NSThread currentThread]);
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i=0; i<3; i++) {
NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@:%@",[NSString stringWithFormat:@"%i",i],[NSThread currentThread]);
}];
// 添加到操作队列
[queue addOperation:bop];
}
// 或者快捷添加操作对象
[queue addOperationWithBlock:^{
NSLog(@"Add directly:%@",[NSThread currentThread]);
}];
}
我们发现,队列虽然是在主线程中执行,但是操作对象都是运行在子线程中,并且所有的操作对象都不需要手动开启,都是由队列自动调度开启。
关于操作对象的执行顺序会根据当前处于isReady
状态的操作对象的优先级调用,另外,设置依赖关系的操作对象会在依赖对象完成后进入isReady
状态,换句话说,被依赖对象优先于依赖对象执行。
首先我们先创建两个操作对象
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block:%@",[NSThread currentThread]);
}];
[bop addExecutionBlock:^{
NSLog(@"blockNew:%@",[NSThread currentThread]);
}];
[bop addExecutionBlock:^{
NSLog(@"blockNew2:%@",[NSThread currentThread]);
}];
NSInvocationOperation *iop = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation:) object:nil];
[queue addOperations:@[iop,bop] waitUntilFinished:NO];
我们发现这两个操作对象的任务都是并发的。
我们为两个操作项添加依赖。
// 添加依赖关系
[iop addDependency:bop];
我们发现,由于iop依赖bop,所以iop始终会等到bop完成后才被执行。
添加依赖时,不要出现循环依赖的情况,会导致死锁,死锁的两个操作对象都等待对方完成,结果都不能执行。
另外,如果A依赖B,B手动先于A执行,系统则会抛出异常。
[iop addDependency:bop];
[iop start];
如果我们需要在所有操作任务完成之后执行一些事务,则可以设置等待。
[queue addOperations:@[iop,bop] waitUntilFinished:YES];
或者
[queue waitUntilAllOperationsAreFinished];
该方法会阻塞当前线程,等待所有操作对象完成任务。在此期间,当前线程不能再往队列中添加操作对象,当然,其他线程可以添加操作对象,一旦所有挂起的操作对象完成,此方法返回,如果队列中没有操作对象,则此方法立刻返回。
@property NSInteger maxConcurrentOperationCount;
默认为-1,不限制,当设置为1时,相当于一个同步执行的操作队列。
可监听的一些属性:
// 操作已取消
@property (readonly, getter=isCancelled) BOOL cancelled;
// 操作正在执行
@property (readonly, getter=isExecuting) BOOL executing;
// 操作已完成
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
// 任务是并发还是同步执行,当操作任务加入到操作队列后,会忽略该属性
@property (readonly, getter=isAsynchronous) BOOL asynchronous ;
// 任务是否已经就绪,当其依赖的操作任务都执行完时,改状态才会是YES
@property (readonly, getter=isReady) BOOL ready;
添加/移除依赖
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;
开启/取消操作任务
- (void)start;
- (void)cancel;
优先级
@property NSOperationQueuePriority queuePriority;
// 取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
阻塞当前线程直到操作完成
- (void)waitUntilFinished;
操作完成回调
@property (nullable, copy) void (^completionBlock)(void);
当前操作数量
@property (readonly) NSUInteger operationCount;
暂停/恢复队列
@property (getter=isSuspended) BOOL suspended;
取消队列中所有操作任务
- (void)cancelAllOperations;
当前队列/主队列
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue;
@property (class, readonly, strong) NSOperationQueue *mainQueue;
我们可以获取到主队列,再调用
- (void)addOperationWithBlock:(void (^)(void))block;
例如在主队列中完成UI的更新。
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.contentImageView.image = image;
}];