NSOperation
NSOperation类是用来封装在单个任务相关的代码和数据的抽象类。NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。
** 因为它是用来封装任务的,大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列,
但是NSOperation本身又有执行多线程的能力跟GCD里的任务还是有区别的** 。
操作步骤也很好理解:
- 将要执行的任务封装到一个 NSOperation 对象中。
- 将此任务添加到一个 NSOperationQueue 对象中。
然后系统就会自动在执行任务。
创建任务
NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation 和 NSBlockOperation 。创建一个 Operation 后,需要调用 start 方法来启动任务,它会默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel 方法即可。
#.创建NSInvocationOperation对象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//开始执行
[operation start];
# 创建一个NSOperation不应该直接调用start方法(如果直接start则会在主线程中调用)而是应该放到NSOperationQueue中启动。
#.创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//开始任务
[operation start];
注意:之前说过这样的任务,默认会在当前线程执行。但是 NSBlockOperation 还有一个方法:addExecutionBlock: ,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务 会并发执行,它会 在主线程和其它的多个线程 执行这些任务.。并且ddExecutionBlock 方法必须在 start() 方法之前执行,否则就会报错。
# 创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
# 添加多个Block
for (NSInteger i = 0; i < 5; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
}];
}
#开始任务,你会发现,会在多个线程中打印,其中包括主线程。
[operation start];
自定义Operation
除了上面的两种 Operation 以外,我们还可以自定义 Operation。自定义 Operation 需要继承 NSOperation 类,并实现其 main() 方法,因为在调用 start() 方法的时候,内部会调用 main() 方法完成相关逻辑。所以如果以上的两个类无法满足你的欲望的时候,你就需要自定义了。你想要实现什么功能都可以写在里面。除此之外,你还需要实现 cancel() 在内的各种方法。
创建队列
我们可以调用一个 NSOperation 对象的 start() 方法来启动这个任务,这个默认是 同步执行 的。就算是 addExecutionBlock 方法,也会在 当前线程和其他线程 中执行,也就是说还是会占用当前线程。如果你不想这个任务在主线程中执行(代码默认情况下都在主线程中执行。)这是就要用到队列 NSOperationQueue 。按类型来说的话一共有两种类型:主队列、其他队列。只要添加到队列,会自动调用任务的 start() 方法。
- 主队列
主队列是串行队列,添加到主队列的任务都会一个接一个地排着队在主线程处理。
# 获取到主队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
-
其他创建的队列
主队列比较特殊,所以会单独有一个类方法来获得主队列。并且其他队列的任务会在其他线程并行执行。请注意这里是并行执行。#1.创建一个其他队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; #2.创建NSBlockOperation对象 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"%@", [NSThread currentThread]); }]; #.添加多个Block for (NSInteger i = 0; i < 5; i++) { [operation addExecutionBlock:^{ NSLog(@"第%ld次:%@", i, [NSThread currentThread]); }]; } #.队列添加任务 [queue addOperation:operation];
NSOperationQueue的一些特殊使用
- 设置最大并发数
我们将 NSOperationQueue 与 GCD的队列 相比较就会发现,这里没有串行队列,那如果我想要10个任务在其他线程串行的执行的话,NSOperationQueue 有一个参数 maxConcurrentOperationCount 最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为 1 的时候,就相当于是串行队列了。
NSOperationQueue 添加一个任务到队列
- (void)addOperationWithBlock:(void (^)(void))block; 这样就可以添加一个任务到队列中了。-
NSOperation 之间添加依赖
NSOperation 有一个非常实用的功能,那就是对任务添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖了:
#1.任务一:下载图片 NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"下载图片 - %@", [NSThread currentThread]); [NSThread sleepForTimeInterval:1.0]; }]; #2.任务二:打水印 NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"打水印 - %@", [NSThread currentThread]); [NSThread sleepForTimeInterval:1.0]; }]; #3.任务三:上传图片 NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"上传图片 - %@", [NSThread currentThread]); [NSThread sleepForTimeInterval:1.0]; }]; #4.设置依赖 [operation2 addDependency:operation1]; //任务二依赖任务一 [operation3 addDependency:operation2]; //任务三依赖任务二 #5.创建队列并加入任务 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
注意
A.不能添加相互依赖,会死锁,比如 A依赖B,B依赖A。
B.可以使用 removeDependency 来解除依赖关系。
C.可以在不同的队列之间依赖,依赖是添加到任务身上的,和队列没关系。 -
线程的挂起
#pragma mark - 线程的挂起 #暂停继续(对队列的暂停和继续),挂起的是队列,不会影响已经在执行的操作 - (IBAction)pause:(UIButton *)sender { //判断操作的数量,当前队列里面是不是有操作? if (self.opQueue.operationCount == 0) { NSLog(@"当前队列没有操作"); return; } self.opQueue.suspended = !self.opQueue.isSuspended; if (self.opQueue.suspended) { NSLog(@"暂停"); }else{ NSLog(@"继续"); }
}
-
取消队列里的所有操作
#pragma mark - 取消队列里的所有操作 - (IBAction)cancelAll:(UIButton *)sender { # 只能取消所有队列的里面的操作,正在执行的无法取消 # 取消操作并不会影响队列的挂起状态 [self.opQueue cancelAllOperations]; NSLog(@"取消队列里所有的操作"); //取消队列的挂起状态 #(只要是取消了队列的操作,我们就把队列处于不挂起状态,以便于后续的开始) self.opQueue.suspended = NO; }
其他方法
以上就是一些主要方法, 下面还有一些常用方法需要大家注意:
# NSOperation
BOOL executing; //判断任务是否正在执行
BOOL finished; //判断任务是否完成
#当一个 operation 被取消时,它的 completion block 仍然会执行,
#所以我们需要在真正执行代码前检查一下 isCancelled 方法的返回值。
- void (^completionBlock)(void); //用来设置完成后需要执行的操作
- (void)cancel; //取消任务
- (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕
# NSOperationQueue
NSUInteger operationCount; //获取队列的任务数
- (void)cancelAllOperations; //取消队列中所有的任务
#阻塞当前线程直到此队列中的所有任务执行完毕
# **可以用来处理所有任务完成后的事件**
- (void)waitUntilAllOperationsAreFinished;
[queue setSuspended:YES]; // 暂停queue
[queue setSuspended:NO]; // 继续queue
NSOperation和GCD的区别和类似的地方
- GCD是纯C语言的API,NSOperationQueue是基于GCD的OC版本封装
- GCD只支持FIFO的队列,NSOperationQueue可以很方便地调整执行顺序、设置最大并发数量
- NSOperationQueue可以在轻松在Operation间设置依赖关系,而GCD需要写很多的代码才能实现
- NSOperationQueue支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)
- GCD的执行速度比NSOperationQueue快
**任务之间没有什么依赖关系,而是需要更高的并发能力:GCD **
任务之间有依赖、或者要监听任务的执行情况、需要暂停、继续任务:NSOperationQueue
小结
以上就是关于 NSOperation和NSOperationQueue 的主要知识了,后期如果有更多内容会同步更新。