NSOperation和NSOperationQueue
NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更简单易用、代码可读性也更高。对熟悉面向对象的程序员来说非常简单易用,不用写复杂的GCD代码,代码可读性也更高。
- 1、可以添加任务依赖,方便控制执行顺序
- 2、可以设定操作执行的优先级
- 3、任务执行状态控制:isReady,isExecuting,isFinished,isCancelled
- 4、可以设置最大并发量
NSOperation
一个抽象类,表示与单个任务关联的代码和数据。
因为NSOperation类是一个抽象类,所以不要直接使用它,而是使用系统定义的子类(NSInvocationOperation或NSBlockOperation)子类或使用其中一个来执行实际任务。尽管是抽象的,但NSOperation的基本实现确实包含了协调安全执行任务的重要逻辑。这种内置逻辑的存在使您可以专注于任务的实际实现,而不是确保它与其他系统对象正常工作所需的粘合代码。
三种方式使用NSOperation
1.使用子类NSInvocationOperation
2.使用子类NSBlockOperation
3.定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。
在不使用操作队列NSOperationQueue,而单独使用NSOperation的情况下。系统只会同步执行操作,下面我们学习一下这三种创建方式。
NSInvocationOperation
两种初始化方法
- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
- (instancetype)initWithInvocation:(NSInvocation *)inv
第一种初始化initWithTarget
/**
* 使用子类 NSInvocationOperation
*/
- (void)useInvocationOperation {
// 1.创建 NSInvocationOperation 对象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
// 2.调用 start 方法开始执行操作
[op start];
}
- (void)task1 {
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}
第二种
initWithInvocation
- (void)InvocationOperation {
NSMethodSignature *sign = [[self class] instanceMethodSignatureForSelector:@selector(task1)];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sign];
invocation.selector = @selector(task1);
invocation.target = self;
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithInvocation:invocation];
[op start];
}
- (void)task1 {
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"%d---%@",i, [NSThread currentThread]); // 打印当前线程
}
}
打断点调试
调用顺序都是一样的
[NSInvocationOperation start]->[NSInvocationOperation main]->[NSInvocation invoke]->具体任务
总结
上述两种创建方式最后都会在主线程里面执行,没有,没有开启新线程,调用NSInvocationOperation的start后调用[NSInvocationOperation main]最后都是通过[NSInvocation invoke]来调用具体方法的。
NSBlockOperation
主要方法
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;
- blockOperationWithBlock
/**
* 使用子类 NSBlockOperation
*/
- (void)useBlockOperation {
// 1.创建 NSBlockOperation 对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
// 2.调用 start 方法开始执行操作
[op start];
}
同样是在主线程执行任务,没有开启新线程。
[NSInvocationOperation start]->[NSInvocationOperation main]->__NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__
__NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK_是让runloop处理一段block,这里简单理解一下就好。
- addExecutionBlock 添加额外的操作
/**
* 使用子类 NSBlockOperation
* 调用方法 AddExecutionBlock:
*/
- (void)useBlockOperationAddExecutionBlock {
// 1.创建 NSBlockOperation 对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
// 2.添加额外的操作
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
}
}];
// 3.调用 start 方法开始执行操作
[op start];
}
由上面的信息我们可以看出,blockOperationWithBlock:方法中的操作是在主线程中执行的,而addExecutionBlock:方法中的操作是在其他线程中执行的,并且只要NSBlockOperation封装的操作数(添加额外的任务) >1,就会异步执行操作。
- 使用定义继承自NSOperation的子类
集成NSOperation创建GPOperation名字任意。代码如下。 - .h
#import
NS_ASSUME_NONNULL_BEGIN
@interface GPOperation : NSOperation
@end
NS_ASSUME_NONNULL_END
.m
#import "GPOperation.h"
@implementation GPOperation
- (void)main {
if (!self.isCancelled) {
for (int i = 0; i < 5; i++) {
NSLog(@"%d-----%@",i,[NSThread currentThread]);
}
}
}
@end
在控制器调用的方法
- (void)myOperation {
GPOperation *op = [[GPOperation alloc] init];
[op start];
}
单独使用自定义子类的情况下,是在主线程执行操作,并没有开启新线程。
NSOperationQueue
主队列
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
自定义队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
队列添加一个操作
- (void)addOperation:(NSOperation *)op;
队列添加一个操作数组
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait
队列添加一个block操作
- (void)addOperationWithBlock:(void (^)(void))block;
1. addOperation
- (void)queueAddoperation {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperation:op1];
[queue addOperation:op2];
}
会开辟新的线程op1,op2并行执行。
2. addOperations
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait
参数说明ops
添加到队列的任务数组,waitUntilFinished
,执行顺序,后续被添加到队列的任务是否需要等待上边的ops
都完成开始执行。
- (void)addOperations {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// queue.maxConcurrentOperationCount = 1;
// 2.创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"5---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperations:@[op1,op2] waitUntilFinished:YES];
[queue addOperations:@[op3,op4] waitUntilFinished:NO];
[queue addOperation:op5];
}
根据addOperations参数定义,可以推测出op3,op4,op5,会等待op1,op2执行完毕才开始。使用情况,一个操作需要在其他一个或者几个操作执行完毕之后在执行,可以用addOperations来控制。
结果如图---
addOperationWithBlock
- (void)addOperationWithBlock:(void (^)(void))block;
示例代码
- (void)addOperationWithBlock {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 1.添加操作
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
}
}];
}
NSOperation addDependency操作添加依赖,也就是说依赖的任务都完成后,才能执行当前任务
/**
* 操作依赖
* 使用方法:addDependency:
*/
- (void)addDependency {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// queue.maxConcurrentOperationCount = 1;
// 2.创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"任务1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"任务2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"任务3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"任务4---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"任务5---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op2 addDependency:op1];
[op3 addDependency:op2];
[op4 addDependency:op3];
[op5 addDependency:op4];
[queue addOperations:@[op1,op2,op3,op4,op5] waitUntilFinished:YES];
}
程序按照添加依赖的顺序来执行的, 注意:不能互相添加依赖我们改一下代码,让op1和op2互相依赖。
/**
* 操作依赖
* 使用方法:addDependency:
*/
- (void)addDependency {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// queue.maxConcurrentOperationCount = 1;
// 2.创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"任务1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"任务2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"任务3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"任务4---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"任务5---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op2 addDependency:op1];
[op1 addDependency:op2];
[op4 addDependency:op3];
[op5 addDependency:op4];
[queue addOperations:@[op1,op2,op3,op4,op5] waitUntilFinished:YES];
}
op1依赖op2,op2依赖op1导致都不会执行,一定要避免这种情况。
队列控制最大并发数概念
@property NSInteger maxConcurrentOperationCount;
maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。
/**
* 设置 MaxConcurrentOperationCount(最大并发操作数)
*/
- (void)maxConcurrentOperationCount {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
// 1.添加操作
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
}
}];
}
任务串行执行
增加maxConcurrentOperationCount = 4看结果
任务并行执行
NSOperation 优先级
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
主要是针对同一队列中,优先级高的任务,调用的几率会更大,但不是绝对的,优先级在资源争夺的时候体现出来的更明显。
线程间通信
模拟在子线程处理耗时操作,回到主线程刷新UI。
/**
* 线程间通信
*/
- (void)communication {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 2.添加操作
[queue addOperationWithBlock:^{
// 异步进行耗时操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
// 回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 进行一些 UI 刷新等操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
}];
}
其他用法
NSOperation
@property (readonly, getter=isExecuting) BOOL executing;//判断操作是否正在在运行。
@property (readonly, getter=isFinished) BOOL finished;//判断操作是否已经结束。
@property (readonly, getter=isReady) BOOL ready;判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。
@property (readonly, getter=isCancelled) BOOL cancelled;//可取消操作,实质是标记 isCancelled 状态。
操作同步
- (void)waitUntilFinished;
阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。
- (void)setCompletionBlock:(void (^)(void))block;
completionBlock 会在当前操作执行完毕时执行 completionBlock。
- (void)addDependency:(NSOperation *)op;
添加依赖,使当前操作依赖于操作 op 的完成。
- (void)removeDependency:(NSOperation *)op;
移除依赖,取消当前操作对操作 op 的依赖。
@property (readonly, copy) NSArray
在当前操作开始执行之前完成执行的所有操作对象数组。
NSOperationQueue 常用属性和方法
取消/暂停/恢复操作
- (void)cancelAllOperations;
可以取消队列的所有操作。
- (BOOL)isSuspended;
判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态。
- (void)setSuspended:(BOOL)b;
可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列。
操作同步
- (void)waitUntilAllOperationsAreFinished;
阻塞当前线程,直到队列中的操作全部执行完毕。
添加/获取操作
- (void)addOperationWithBlock:(void (^)(void))block;
向队列中添加一个 NSBlockOperation 类型操作对象。
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;
向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
- (NSArray *)operations;
当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)。
- (NSUInteger)operationCount;
当前队列中的操作数。
获取队列
+ (id)currentQueue;
获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。
+ (id)mainQueue;
获取主队列。
这里的暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。
iOS 多线程:『NSOperation、NSOperationQueue』详尽总结