美文网首页iOS
iOS详解多线程(实现篇——NSOperation)

iOS详解多线程(实现篇——NSOperation)

作者: 小曼blog | 来源:发表于2020-10-16 17:31 被阅读0次
    NSOperation.png

    上一节中,我们探究了GCD实现多线程的各种方式,有图有真相,不清楚的朋友们可以回去看一看啦。这一节中,我们来看看苹果官方给我们提供的又一个实现多线程的方式,NSOperation。

    GCD链接:iOS详解多线程(实现篇——GCD)
    NSThread链接:详解多线程(实现篇——NSThread)
    多线程概念篇链接:详解多线程(概念篇——进程、线程以及多线程原理)

    源码链接:https://github.com/weiman152/Multithreading.git

    多线程的实现方法

    1.NSThread(OC)

    2.GCD(C语言)

    3.NSOperation(OC)

    4.C语言的pthread(C语言)
    5.其他实现多线程方法

    本节主要内容

    1. NSOperation是什么
    2. NSOperation的使用
      2_1.NSOperation对象的创建(三种方式)
      2_2. NSOperationQueue的使用
      2_3. 任务和队列结合使用
    3. NSOperation其他重要用法
      3_1.NSOperation的依赖
    4. 案例
    1.NSOperation是什么

    NSOperation是苹果官方提供的面向对象的一种解决多线程的方式。NSOperation是对GCD的封装。我们已经知道,GCD是C语言风格的,NSOperation是OC的面向对象的,比GCD多了一些更加简单实用的功能,使用起来更加的便捷,容易理解。
    NSOperation 和NSOperationQueue 分别对应 GCD 的 任务 和 队列。

    2.NSOperation的使用

    我们现在知道,NSOperation是对GCD的封装,复习一下GCD的使用步骤:
    1.创建队列(串行、并发);
    2.创建任务(同步、异步);
    把任务放在队列中执行。

    NSOperation的使用也是差不多的步骤:
    1.将任务封装到NSOperation对象中;
    2.将NSOperation对象添加到NSOperationQueue中;
    系统会自动将NSOperationQueue中的NSOperation取出来,并将取出的NSOperation封装的操作放到一条新线程中执行。

    2.1 NSOperation对象的创建
    注意:NSOperation是一个抽象类,不具备封装操作的能力,必须使用它的子类。

    NSOperation有三个子类都可以创建任务对象。
    1》NSInvocationOperation

    先创建一个待执行的函数,run

    -(void)runWithName:(NSString *)name{
        NSLog(@"-------%@-------",name);
        NSLog(@"当前线程:%@",[NSThread currentThread]);
        NSLog(@"哈哈哈哈,我是个任务,要执行2秒哦");
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任务结束啦");
    }
    

    NSInvocationOperation执行run方法。

    - (IBAction)test1:(id)sender {
        //1.NSInvocationOperation
        NSInvocationOperation * invocationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(runWithName:) object:@"NSInvocationOperation"];
        //启动
        [invocationOp start];
    }
    

    结果:


    image.png

    2》NSBlockOperation
    NSBlockOperation是最常用的一种方式了,可以追加任务,并且追加的任务是在子线程中执行的。

    - (IBAction)test2:(id)sender {
        //2.NSBlockOperation(最常使用)
        NSBlockOperation * blockOp = [NSBlockOperation blockOperationWithBlock:^{
            //要执行的操作,目前是主线程
            NSLog(@"NSBlockOperation 创建,线程:%@",[NSThread currentThread]);
        }];
        //2.1 追加任务,在子线程中执行
        [blockOp addExecutionBlock:^{
            NSLog(@"追加任务一");
            [self runWithName:@"NSBlockOperation 追加"];
        }];
        [blockOp addExecutionBlock:^{
            NSLog(@"追加任务二, %@",[NSThread currentThread]);
        }];
        [blockOp start];
    }
    

    结果:


    image.png

    3》自定义类继承自NSOperation,实现main方法。
    我们创建一个类WMOperation继承自NSOperation,如下图:


    image.png

    实现main方法:


    image.png

    使用:

    - (IBAction)test3:(id)sender {
        WMOperation * wmOp = [[WMOperation alloc] init];
        [wmOp start];
    }
    

    结果:


    image.png

    上面是最简单的自定义Operation,是一种串行操作,也是在主线程进行的操作。为了更好的探究自定义类,我们再新建两个类,一个是复杂一点的串行,一个是并行操作。
    新建两个类,如下图:


    image.png

    WMCXOperation:

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    /**
     自定义串行操作,NSOperation的子类
     */
    @interface WMCXOperation : NSOperation
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    #import "WMCXOperation.h"
    
    @implementation WMCXOperation
    
    - (void)main {
        NSLog(@"main 开始啦");
        @try {
            //任务是否结束标识
            BOOL isFinish = NO;
            //只有当没有执行完成和没有被取消,才执行自定义的相应操作
            while (isFinish==NO&&(self.isCancelled==NO)) {
                //睡眠1秒,模拟耗时
                sleep(1);
                NSLog(@"线程:%@",[NSThread currentThread]);
                isFinish = YES;
            }
        } @catch (NSException *exception) {
            NSLog(@"出现异常:%@",exception);
        } @finally {
            NSLog(@"哈哈哈");
        }
        NSLog(@"main 结束啦");
    }
    
    @end
    
    

    测试:

    - (IBAction)test3:(id)sender {
        WMOperation * wmOp = [[WMOperation alloc] init];
        [wmOp start];
        
        //自定义串行
        WMCXOperation * cxOp = [[WMCXOperation alloc] init];
        NSLog(@"任务开始");
        [cxOp start];
        NSLog(@"任务结束");
        
        //自定义并行
    }
    

    结果:


    image.png

    从结果可以看出,任务的执行依然是在主线程,是串行操作。

    自定义并行操作:
    WMBXOperation

    //
    //  WMBXOperation.h
    //  Multithreading
    //
    //  Created by wenhuanhuan on 2020/10/14.
    //  Copyright © 2020 weiman. All rights reserved.
    //
    
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    /**
    自定义并行操作,NSOperation的子类
     
     自定义并行的 NSOperation 则要复杂一点,首先必须重写以下几个方法:
    
     start: 所有并行的 Operations 都必须重写这个方法,然后在你想要执行的线程中手动调用这个方法。注意:任何时候都不能调用父类的start方法。
     main: 在start方法中调用,但是注意要定义独立的自动释放池与别的线程区分开。
     isExecuting: 是否执行中,需要实现KVO通知机制。
     isFinished: 是否已完成,需要实现KVO通知机制。
     isConcurrent: 该方法现在已经由isAsynchronous方法代替,并且 NSOperationQueue 也已经忽略这个方法的值。
     isAsynchronous: 该方法默认返回 NO ,表示非并发执行。并发执行需要自定义并且返回 YES。后面会根据这个返回值来决定是否并发。
     与非并发操作不同的是,需要另外自定义一个方法来执行操作而不是直接调用start方法.
    */
    @interface WMBXOperation : NSOperation
    
    //自定义方法,启动操作
    -(BOOL)wmStart:(NSOperation *)op;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    //
    //  WMBXOperation.m
    //  Multithreading
    //
    //  Created by wenhuanhuan on 2020/10/14.
    //  Copyright © 2020 weiman. All rights reserved.
    //
    
    #import "WMBXOperation.h"
    
    @interface WMBXOperation()
    {
        BOOL executing;
        BOOL finished;
    }
    @end
    
    @implementation WMBXOperation
    
    //重写init方法
    -(instancetype)init{
        if (self = [super init]) {
            executing = NO;
            finished = NO;
        }
        return self;
    }
    
    //重写start方法
    -(void)start{
        //如果任务被取消了
        if (self.isCancelled) {
            [self willChangeValueForKey:@"isFinished"];
            finished = YES;
            [self didChangeValueForKey:@"isFinished"];
            return;
        }
        
        [self willChangeValueForKey:@"isExecuting"];
        //使用NSThread开辟子线程执行main方法
        [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
        executing = YES;
        [self didChangeValueForKey:@"isExecuting"];
    }
    
    //重写main方法
    -(void)main {
        NSLog(@"main 开始");
        @try {
            // 必须为自定义的 operation 提供 autorelease pool,因为 operation 完成后需要销毁。
            @autoreleasepool {
                BOOL isfinish = NO;
                while (isfinish==NO&&self.isCancelled==NO) {
                    NSLog(@"线程: %@", [NSThread currentThread]);
                    isfinish = YES;
                }
                [self completeOperation];
            }
        } @catch (NSException *exception) {
            NSLog(@"异常:%@",exception);
        } @finally {
            NSLog(@"嘿嘿");
        }
        
        NSLog(@"main 结束");
    }
    
    - (void)completeOperation {
        [self willChangeValueForKey:@"isFinished"];
        [self willChangeValueForKey:@"isExecuting"];
        
        executing = NO;
        finished = YES;
        
        [self didChangeValueForKey:@"isExecuting"];
        [self didChangeValueForKey:@"isFinished"];
    }
    
    -(BOOL)isAsynchronous{
        return YES;
    }
    
    -(BOOL)isExecuting{
        return executing;
    }
    
    -(BOOL)isFinished{
        return finished;
    }
    
    -(BOOL)wmStart:(NSOperation *)op{
        BOOL run = NO;
        if ([op isReady]&&![op isCancelled]) {
            if ([op isAsynchronous]==NO) {
                [op start];
            }else{
                [NSThread detachNewThreadSelector:@selector(start) toTarget:op withObject:nil];
            }
            run = YES;
        }else if ([op isCancelled]){
            [self willChangeValueForKey:@"isFinished"];
            [self willChangeValueForKey:@"isExecuting"];
            executing = NO;
            finished = YES;
            [self didChangeValueForKey:@"isExecuting"];
            [self didChangeValueForKey:@"isFinished"];
            run = YES;
        }
        
        return run;
    }
    
    @end
    
    

    测试:

    - (IBAction)test3:(id)sender {
        WMOperation * wmOp = [[WMOperation alloc] init];
        [wmOp start];
        
        //自定义串行
        WMCXOperation * cxOp = [[WMCXOperation alloc] init];
        NSLog(@"任务开始");
        [cxOp start];
        NSLog(@"任务结束");
        
        //自定义并行
        WMBXOperation * bxOp = [[WMBXOperation alloc] init];
        NSLog(@"并行任务开始");
        [bxOp wmStart:bxOp];
        NSLog(@"并行任务结束");
    }
    

    打印结果:


    image.png

    从结果可以看出,是在新的子线程中执行的任务。
    当然了,因为我们再自定义类的代码中使用了NSthread的开启子线程的方法:


    image.png
    2.2 NSOperationQueue的使用

    NSOperationQueue相当于GCD的队列。NSOperation有两种队列:

    1.主队列:mainQueue,在主队列中的任务都在主线程执行。
    2.其他队列:非主队列通过设置最大并发数确定是串行还是并发队列。

    //主队列
    - (IBAction)mainQ:(id)sender {
        NSOperationQueue * q1 = [NSOperationQueue mainQueue];
    }
    
    //非主队列
    - (IBAction)otherQ:(id)sender {
        NSOperationQueue * q2 = [[NSOperationQueue alloc] init];
    }
    

    NSOperationQueue的作用

    NSOperation的子类对象是通过调用start方法来启动任务的。如果将对象添加到NSOperationQueue中,就不需要手动启动了。

    添加任务到队列的两个方法:
    -(void)addOperation:(NSOperation *)op;
    -(void)addOperationWithBlock:(void (^)(void))block;

    下面,我们就把任务和队列结合起来,实现多线程。

    2.3 NSOperation子类和NSOperationQueue结合使用创建多线程

    NSBlockOperation 不论封装操作还是追加操作都是异步并发执行

    1》NSBlockOperation+主队列

    //block+主队列
    - (IBAction)blockAndMain:(id)sender {
        //1.创建主队列
        NSOperationQueue * q1 = [NSOperationQueue mainQueue];
        //2.创建任务
        NSBlockOperation * p1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务一,当前线程:%@",[NSThread currentThread]);
            [NSThread sleepForTimeInterval:2.0];
            NSLog(@"任务一结束");
        }];
        [p1 addExecutionBlock:^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任务二,线程:%@",[NSThread currentThread]);
        }];
        [p1 addExecutionBlock:^{
            NSLog(@"任务三,线程:%@",[NSThread currentThread]);
        }];
        //3.把任务添加到队列
        [q1 addOperation:p1];
        
        //也可以直接添加操作到队列中
        [q1 addOperationWithBlock:^{
            NSLog(@"直接添加操作,%@",[NSThread currentThread]);
        }];
    }
    

    结果:


    image.png

    从结果可以看出,三个操作是并发执行的,虽然是在主队列中添加任务,但是任务并不是都在主线程执行,而是有在主线程也有在子线程中并发执行的。

    2》NSBlockOperation+非主队列

    //block+非主队列
    - (IBAction)blockAndOther:(id)sender {
        //创建非主队列
        NSOperationQueue * q1 = [[NSOperationQueue alloc] init];
        NSBlockOperation * b1 = [[NSBlockOperation alloc] init];
        [b1 addExecutionBlock:^{
            NSLog(@"任务一,线程:%@",[NSThread currentThread]);
        }];
        [b1 addExecutionBlock:^{
            [NSThread sleepForTimeInterval:2.0];
            NSLog(@"任务二,线程:%@",[NSThread currentThread]);
        }];
        [b1 addExecutionBlock:^{
            NSLog(@"任务三,线程:%@",[NSThread currentThread]);
        }];
        //把任务添加到队列
        [q1 addOperation:b1];
    }
    

    结果:

    image.png

    其实,我们只使用NSBlockOperation也是可以实现多线程的,只是需要我们自己手动开启任务。

    //block+非主队列
    - (IBAction)blockAndOther:(id)sender {
        //创建非主队列
        NSOperationQueue * q1 = [[NSOperationQueue alloc] init];
        NSBlockOperation * b1 = [[NSBlockOperation alloc] init];
        [b1 addExecutionBlock:^{
            NSLog(@"任务一,线程:%@",[NSThread currentThread]);
        }];
        [b1 addExecutionBlock:^{
            [NSThread sleepForTimeInterval:2.0];
            NSLog(@"任务二,线程:%@",[NSThread currentThread]);
        }];
        [b1 addExecutionBlock:^{
            NSLog(@"任务三,线程:%@",[NSThread currentThread]);
        }];
        //把任务添加到队列
        [q1 addOperation:b1];
        
        //只使用NSBlockOperation
        NSLog(@"-------只使用NSBlockOperation实现------------");
        NSBlockOperation * b2 = [[NSBlockOperation alloc] init];
        [b2 addExecutionBlock:^{
            NSLog(@"1,线程:%@",[NSThread currentThread]);
        }];
        [b2 addExecutionBlock:^{
            [NSThread sleepForTimeInterval:2.0];
            NSLog(@"2,线程:%@",[NSThread currentThread]);
        }];
        [b2 addExecutionBlock:^{
            NSLog(@"3,线程:%@",[NSThread currentThread]);
        }];
        [b2 start];
    }
    

    结果:


    image.png

    3》NSInvocationOperation+主队列

    - (IBAction)InvoAndMain:(id)sender {
        NSOperationQueue * mainQ = [NSOperationQueue mainQueue];
        NSInvocationOperation * p1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
        NSInvocationOperation * p2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];
        NSInvocationOperation * p3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task3) object:nil];
        [mainQ addOperation:p1];
        [mainQ addOperation:p2];
        [mainQ addOperation:p3];
    }
    
    -(void)task1 {
        NSLog(@"任务一, %@",[NSThread currentThread]);
    }
    
    -(void)task2 {
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任务二, %@",[NSThread currentThread]);
    }
    
    -(void)task3 {
        NSLog(@"任务三, %@",[NSThread currentThread]);
    }
    

    结果:


    image.png

    任务在主队列顺序执行,也就是串行。

    4》NSInvocationOperation+非主队列

    - (IBAction)invoAndOther:(id)sender {
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        NSInvocationOperation * p1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
        NSInvocationOperation * p2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];
        NSInvocationOperation * p3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task3) object:nil];
        [queue addOperation:p1];
        [queue addOperation:p2];
        [queue addOperation:p3];
    }
    
    -(void)task1 {
        NSLog(@"任务一, %@",[NSThread currentThread]);
    }
    
    -(void)task2 {
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任务二, %@",[NSThread currentThread]);
    }
    
    -(void)task3 {
        NSLog(@"任务三, %@",[NSThread currentThread]);
    }
    

    结果:


    image.png

    由结果可以看出,三个任务并发执行。

    3. NSOperation其他重要用法
    3.1 NSOperation的依赖 - (void)addDependency:(NSOperation *)op;

    有时候,我们需要给任务添加依赖关系,比如有两个任务op1和op2,任务2必须要等待任务1执行完成之后才能执行,这个时候就可以添加任务2依赖于任务1 .

    //任务依赖
    - (IBAction)test1:(id)sender {
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        NSBlockOperation * op1 = [[NSBlockOperation alloc] init];
        [op1 addExecutionBlock:^{
            NSLog(@"任务一, %@",[NSThread currentThread]);
        }];
        [op1 addExecutionBlock:^{
            [NSThread sleepForTimeInterval:2.0];
            NSLog(@"任务二,%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op2 = [[NSBlockOperation alloc] init];
        [op2 addExecutionBlock:^{
            NSLog(@"op2哦,%@",[NSThread currentThread]);
        }];
        //设置op2依赖于任务op1
        //[op2 addDependency:op1];
        [queue addOperation:op1];
        [queue addOperation:op2];
    }
    

    我们在这里有连个任务,op1和op2,两个任务都在非主队列中执行,我们先不添加依赖,看看执行结果:


    image.png

    从结果可以看出,任务一有两个操作,任务二有一个操作,它们分别在不同的线程中执行,不分先后。
    添加依赖看看:


    image.png
    打印结果:
    image.png

    从结果可以看出,添加了依赖之后,任务二只会在任务一执行完成之后才会执行。

    3.2 NSOperation执行完成 completionBlock

    如果想要在某个操作执行完成之后在执行某种操作,这个时候就可以使用completionBlock了。

    //NSOperation执行完成
    - (IBAction)test2:(id)sender {
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务一,%@",[NSThread currentThread]);
        }];
        [op1 addExecutionBlock:^{
            [NSThread sleepForTimeInterval:2.0];
            NSLog(@"任务二,%@",[NSThread currentThread]);
        }];
        [op1 addExecutionBlock:^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任务三,%@",[NSThread currentThread]);
        }];
        [op1 addExecutionBlock:^{
            NSLog(@"任务四,%@",[NSThread currentThread]);
        }];
        op1.completionBlock = ^{
            NSLog(@"任务都执行完成啦");
        };
        [queue addOperation:op1];
    }
    

    打印结果:


    image.png

    从结果可以看出,四个任务在四个子线程中完成,直到所有的任务都执行完,才执行了completionBlock内的代码。

    3.3 最大并发数 maxConcurrentOperationCount

    我们可以设置队列的最大并发数属性,控制队列是并发、串行还是不执行。
    maxConcurrentOperationCount>1: 并发
    maxConcurrentOperationCount=1:串行
    maxConcurrentOperationCount=-1:不限制,默认值
    maxConcurrentOperationCount=0:不会执行任何操作
    我们一起来试试。

    • 串行
    //maxConcurrentOperationCount 最大并发数
    - (IBAction)test3:(id)sender {
        //串行
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        queue.maxConcurrentOperationCount = 1;
        NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务一,%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务二,%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务三,%@",[NSThread currentThread]);
        }];
        [queue addOperation:op1];
        [queue addOperation:op2];
        [queue addOperation:op3];
    }
    
    

    打印结果:


    image.png

    只开辟了一个线程,任务一个个的执行。我们把任务二改成耗时操作,看看会不会阻塞当前线程。


    image.png

    看看打印结果:


    image.png
    依然是顺序执行,说明会阻塞当前线程。

    注意:如果我们是一个操作中的三个子任务,我们设置队列的最大并发数是没有效果的,例如:

    //2. 一个操作的多个任务,设置最大并发数为1是没有效果的
        NSOperationQueue * queue2 = [[NSOperationQueue alloc] init];
        queue2.maxConcurrentOperationCount = 1;
        NSBlockOperation * op4 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"1,%@",[NSThread currentThread]);
        }];
        [op4 addExecutionBlock:^{
            [NSThread sleepForTimeInterval:2.0];
            NSLog(@"2,%@",[NSThread currentThread]);
        }];
        [op4 addExecutionBlock:^{
            NSLog(@"3,%@",[NSThread currentThread]);
        }];
        [queue2 addOperation:op4];
    

    结果:


    image.png

    我们发现,即使我们设置了队列的最大并发数为1,由于我们只有一个操作,这个操作中有三个任务,这三个任务还是在三个线程中执行的,也不是顺序的。由此可见,最大操作数针对的是操作,也就是NSBlockOperation对象,而不是任务。

    • 并行
      我们设置最大并发数大于1,看看结果。
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        queue.maxConcurrentOperationCount = 5;
        NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务一,%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
            [NSThread sleepForTimeInterval:2.0];
            NSLog(@"任务二,%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务三,%@",[NSThread currentThread]);
        }];
        [queue addOperation:op1];
        [queue addOperation:op2];
        [queue addOperation:op3];
    

    打印结果:


    image.png

    我们发现三个任务是并发执行的,符合我们的预期。

    • 不执行操作,设置为0
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        queue.maxConcurrentOperationCount = 0;
        NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务一,%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
            [NSThread sleepForTimeInterval:2.0];
            NSLog(@"任务二,%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务三,%@",[NSThread currentThread]);
        }];
        [queue addOperation:op1];
        [queue addOperation:op2];
        [queue addOperation:op3];
    

    当我们设置了
    queue.maxConcurrentOperationCount = 0;
    的时候,发现不会有任何打印,操作都不再执行。

    3.4 队列暂停 suspended

    当我们设置了suspended=YES之后,队列就会暂停。

    //队列暂停,suspended
    - (IBAction)test4:(id)sender {
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        queue.suspended = YES
        NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务一,%@",[NSThread currentThread]);
        }];
        [op1 addExecutionBlock:^{
            NSLog(@"任务二开始,%@",[NSThread currentThread]);
            for (int i=0; i<10; i++) {
                [NSThread sleepForTimeInterval:1.0];
                NSLog(@"2: i=%d",i);
            }
            NSLog(@"任务二结束");
        }];
        [queue addOperation:op1];
    }
    

    这个时候,当我们点击按钮,是不会有任何操作执行的,因为队列暂停了。

    3.5 取消队列的未执行的所有操作
    @interface OperationOtherController ()
    
    @property(nonatomic, strong)NSOperationQueue * myQueue;
    
    @end
    
    //取消所有任务
    - (IBAction)test5:(id)sender {
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"操作一:1,%@",[NSThread currentThread]);
        }];
        [op1 addExecutionBlock:^{
            NSLog(@"操作一:2,%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"操作二:1,%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
            [NSThread sleepForTimeInterval:2.0];
            NSLog(@"操作三:1,%@",[NSThread currentThread]);
        }];
        [queue addOperation:op1];
        [queue addOperation:op2];
        [queue addOperation:op3];
        self.myQueue = queue;
    }
    
    - (IBAction)cancelTest5:(id)sender {
        [self.myQueue cancelAllOperations];
    }
    

    打印结果:


    image.png

    我们发现,即使我们点击了取消按钮,未执行的操作三并没有被取消。

    注意:暂停和取消只能暂停或取消处于等待状态的任务,不能暂停或取消正在执行中的任务,必须等正在执行的任务执行完毕之后才会暂停,如果想要暂停或者取消正在执行的任务,可以在每个任务之间即每当执行完一段耗时操作之后,判断是否任务是否被取消或者暂停。如果想要精确的控制,则需要将判断代码放在任务之中,但是不建议这么做,频繁的判断会消耗太多时间

    3.6 打印队列中所有的操作对象
    - (IBAction)test6:(id)sender {
        NSLog(@"打印所有操作");
        
        NSLog(@"%@",self.myQueue.operations);
    }
    

    结果:


    image.png
    1. 案例:下载图片并合成
    //
    //  OperationCaseController.m
    //  Multithreading
    //
    //  Created by wenhuanhuan on 2020/10/16.
    //  Copyright © 2020 weiman. All rights reserved.
    //
    
    #import "OperationCaseController.h"
    
    @interface OperationCaseController ()
    @property (weak, nonatomic) IBOutlet UIImageView *imageV1;
    @property (weak, nonatomic) IBOutlet UIImageView *imageV2;
    @property (weak, nonatomic) IBOutlet UIImageView *imageVFinal;
    @property(nonatomic,strong)UIImage * image1;
    @property(nonatomic,strong)UIImage * image2;
    @end
    
    @implementation OperationCaseController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
    }
    
    - (IBAction)startAction:(id)sender {
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        //下载图片一
        NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSString * str = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1601638749466&di=92ace2ffa924fe6063e7a221729006b1&imgtype=0&src=http%3A%2F%2Fpic.autov.com.cn%2Fimages%2Fcms%2F20119%2F6%2F1315280805177.jpg";
            UIImage * image = [self downLoadImage:str];
            self.image1 = image;
            //回到主线程,显示图片
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                self.imageV1.image = image;
            }];
        }];
        //下载图片二
        NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
            NSString * str = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1601638873771&di=07129fd95c56096a4282d3b072594491&imgtype=0&src=http%3A%2F%2Fimg.51miz.com%2Fpreview%2Felement%2F00%2F01%2F12%2F49%2FE-1124994-5FFE5AC7.jpg";
            UIImage * image = [self downLoadImage:str];
            self.image2 = image;
            //回到主线程,显示图片
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                self.imageV2.image = image;
            }];
        }];
        //合成图片
        NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
            UIImage * image = [self makeImage:self.image1 image2:self.image2];
            //回到主线程,显示图片
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                self.imageVFinal.image = image;
            }];
        }];
        //由于合成图片要在图片一和图片二完成之后才能进行,所以需要添加依赖
        [op3 addDependency:op1];
        [op3 addDependency:op2];
        [queue addOperation:op1];
        [queue addOperation:op2];
        [queue addOperation:op3];
    }
    
    -(UIImage *)downLoadImage:(NSString *)str {
        NSLog(@"当前线程:%@",[NSThread currentThread]);
        NSURL * url = [NSURL URLWithString:str];
        NSData * data = [NSData dataWithContentsOfURL:url];
        UIImage * image = [UIImage imageWithData:data];
        return image;
    }
    
    -(UIImage *)makeImage:(UIImage *)image1 image2:(UIImage *)image2 {
        //图形上下文开启
        UIGraphicsBeginImageContext(CGSizeMake(300, 200));
        
        //图形二
        [image2 drawInRect:CGRectMake(0, 0, 300, 200)];
        //图形一
        [image1 drawInRect:CGRectMake(100, 50, 100, 100)];
        //获取新的图片
        UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
        //关闭上下文
        UIGraphicsEndImageContext();
        return image;
    }
    
    @end
    
    

    运行结果:


    image.png

    以上就是关于NSOperation创建多线程的探究内容了,如有错漏还请指教。
    祝大家生活愉快。

    相关文章

      网友评论

        本文标题:iOS详解多线程(实现篇——NSOperation)

        本文链接:https://www.haomeiwen.com/subject/tykoyktx.html