iOS多线程之NSOperation、NSOperationQueue

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]); // 打印当前线程
    }
}

Snip20190331_3.png

第二种 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]); // 打印当前线程
    }
}

Snip20190331_4.png

打断点调试


iOS多线程之NSOperation、NSOperationQueue_第1张图片
Snip20190331_6.png
iOS多线程之NSOperation、NSOperationQueue_第2张图片
Snip20190331_5.png

调用顺序都是一样的

[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];
}
Snip20190331_7.png

同样是在主线程执行任务,没有开启新线程。


iOS多线程之NSOperation、NSOperationQueue_第3张图片
Snip20190331_8.png

[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];
}

iOS多线程之NSOperation、NSOperationQueue_第4张图片
Snip20190331_9.png

由上面的信息我们可以看出,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];
}
iOS多线程之NSOperation、NSOperationQueue_第5张图片
Snip20190331_10.png

单独使用自定义子类的情况下,是在主线程执行操作,并没有开启新线程。

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];
}

iOS多线程之NSOperation、NSOperationQueue_第6张图片
Snip20190331_11.png

会开辟新的线程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];
}

Snip20190331_13.png

根据addOperations参数定义,可以推测出op3,op4,op5,会等待op1,op2执行完毕才开始。使用情况,一个操作需要在其他一个或者几个操作执行完毕之后在执行,可以用addOperations来控制。
结果如图---
iOS多线程之NSOperation、NSOperationQueue_第7张图片
Snip20190331_12.png

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]); // 打印当前线程
        }
    }];
}

iOS多线程之NSOperation、NSOperationQueue_第8张图片
Snip20190331_14.png

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];
}

iOS多线程之NSOperation、NSOperationQueue_第9张图片
Snip20190331_16.png

程序按照添加依赖的顺序来执行的, 注意:不能互相添加依赖我们改一下代码,让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];
}

iOS多线程之NSOperation、NSOperationQueue_第10张图片
Snip20190331_17.png

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]); // 打印当前线程
        }
    }];
}

iOS多线程之NSOperation、NSOperationQueue_第11张图片
Snip20190331_18.png

任务串行执行
增加maxConcurrentOperationCount = 4看结果
iOS多线程之NSOperation、NSOperationQueue_第12张图片
Snip20190331_19.png

任务并行执行

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 *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。

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』详尽总结

你可能感兴趣的:(iOS多线程之NSOperation、NSOperationQueue)