美文网首页iOSios进阶iOS 开发继续加油
iOS底层原理总结 - NSOperation详尽总结

iOS底层原理总结 - NSOperation详尽总结

作者: 二斤寂寞 | 来源:发表于2019-05-20 22:13 被阅读29次

    目录:
    1.NSOperation简介
    2.NSOperation的创建
    2.1NSInvocationOperation
    2.2NSBlockOperation(最常用)
    2.3自定义子类继承NSOperation,实现内部相应的方法
    3.NSOperation的重要属性和方法
    3.1依赖
    3.2操作监听
    3.3NSOperation的一些其他属性和方法
    4.1NSOperationQueue的重要属性和方法
    4.1.1最大并发数
    4.1.2线程的挂起
    4.1.3取消队列里的所有操作
    4.1.4依赖关系
    4.2NSOperationQueue的一些其他属性和方法
    5.NSOperation和NSOperationQueue结合使用创建多线程
    6.NSOperation线程之间的通信


    1.NSOperation简介

    NSOperation-Demo
    NSOperation是苹果推荐使用的并发技术,它提供了一些用GCD不是很好实现的功能。
    NSOperation是基于GCD的面向对象的使用OC语言的封装。相比GCD,NSOperation的使用更加简单。

    2.NSOperation的创建

    NSOperation是一个抽象类,也就是说它并不能直接使用,而是应该使用它的子类。使用它的子类的方法有三种,使用苹果为我们提供的两个子类 NSInvocationOperationNSBlockOperation自定义继承自NSOperation的子类。
    NSOperation的使用常常是配合NSOperationQueue来进行的。只要是使用NSOperation的子类创建的实例就能添加到NSOperationQueue操作队列之中,一旦添加到队列,操作就会自动异步执行(注意是异步)。如果没有添加到队列,而是使用start方法,则会在当前线程执行。

    我们知道,线程间的通信主要是主线程与分线程之间进行的。主线程到分线程,NSOperation子类也有相应带参数的方法;而分线程到主线程,比如更新UI,它也有很方便的获取主队列(被添加到主队列的操作默认会在主线程执行)的方法:[NSOperationQueue mainQueue]

    2.1NSInvocationOperation
    • 单个NSInvocationOperation
    - (void)invocationOperation {
        /*
         第一个参数:目标对象
         第二个参数:选择器,要调用的方法
         第三个参数:方法要传递的参数
         */
        NSOperation *op = [[NSInvocationOperation alloc]initWithTarget:self
                                                              selector:@selector(downloadImage:) object:@"Invocation"];
        //2.start方法,直接在当前线程执行
        [op start];
    }
    
    - (void)downloadImage:(id)obj{
      NSLog(@"%@-----%@",[NSThread currentThread],obj);
    }
    

    运行结果:


    NSInvocationOperation.png

    注意:默认情况下,调用了start方法后并不会开一条新的线程去执行,而是在当前线程同步执行操作,只有将NSOperation放到一个 NSOperationQueue 中,才会异步执行操作

    • 添加到NSOperationQueue:
    - (void)invocationOperationAddQueue {
        //1.创建
        NSOperation *op = [[NSInvocationOperation alloc]initWithTarget:self
                                                              selector:@selector(downloadImage:) object:@"Invocation"];
        //2.放到队列里面去
        NSOperationQueue *q = [[NSOperationQueue alloc]init];
        //只要把操作放到队列,会自动异步执行调度方法
        [q addOperation:op];
    }
    

    运行结果:


    invocationOperationAddQueue.png

    可以看出:
    在number为3,name为空的子线程执行。

    • 多个NSInvocationOperation
    - (void)invocationOperationMore {
        //队列,GCD里面的并发队列使用最多,所以NSOperation技术直接把GCD里面的并发队列封装起来
        //NSOperationQueue本质就是GCD里面的并发队列
        //操作就是GCD里面异步执行的任务
        NSOperationQueue *q = [[NSOperationQueue alloc]init];
        //把多个操作放到队列里面
        for (int i = 0; i < 20; i++) {
            NSOperation *op = [[NSInvocationOperation alloc]initWithTarget:self
                                                                  selector:@selector(downloadImage:) object:[NSString stringWithFormat:@"Invocation%d",i]];
            [q addOperation:op];
        }
    }
    

    运行结果:


    invocationOperationMore.png

    可以看出:
    线程名与输出均没有规律,很明显就是并发队列。

    2.2NSBlockOperation(最常用)

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

    - (void)blockOperation1 {
        //跟GCD中的并发队列一样
        NSOperationQueue *q = [[NSOperationQueue alloc]init];
        //跟GCD中的主队列一样
        // NSOperationQueue *q = [NSOperationQueue mainQueue];
        //把多个操作放到队列里面
        for (int i = 0; i < 5; i++) {
            NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
                NSLog(@"%@------%d",[NSThread currentThread],i);
                
            }];
            //把Block操作放到队列
            [q addOperation:op];
        }
        NSLog(@"完成");
    }
    

    方式二:

    - (void)blockOperation2 {
        
        NSOperationQueue *q = [[NSOperationQueue alloc]init];
        for (int i = 0; i < 5; i++) {
            [q addOperationWithBlock:^{
                NSLog(@"%@------%d",[NSThread currentThread],i);
            }];
        }
    }
    

    方式三:

    - (void)blockOperation3 {
        //1.封装操作
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            // 要执行的操作,在主线程中执行
            NSLog(@"1------%@",[NSThread currentThread]);
        }];
        //2.追加操作,追加的操作在子线程中执行,可以追加多条操作
        [op addExecutionBlock:^{
            NSLog(@"---download2--%@",[NSThread currentThread]);
        }];
        
        [op addExecutionBlock:^{
            NSLog(@"---download3--%@",[NSThread currentThread]);
        }];
        
        [op addExecutionBlock:^{
            NSLog(@"---download4--%@",[NSThread currentThread]);
        }];
        [op start];
    }
    

    运行结果:


    NSBlockOperation.png

    可以看出:
    异步执行代码。

    2.3自定义子类继承NSOperation,实现内部相应的方法

    需要实现- (void)main方法,需要做的事情放在mian方法中
    自定义类封装性高,复用性高。

    // 重写自定义类的main方法实现封装操作
    -(void)main
    {
        // 要执行的操作
    }
    
    // 实例化一个自定义对象,并执行操作
    CLOperation *op = [[CLOperation alloc]init];
    [op start];
    

    3.NSOperation的重要属性和方法

    NSOperation中的两种队列
    主队列:通过mainQueue获得,凡是放到主队列中的任务都将在主线程执行
    非主队列:直接alloc init出来的队列。非主队列同时具备了并发和串行的功能,通过设置最大并发数属性来控制任务是并发执行还是串行执行

    3.1依赖
    // 操作op1依赖op5,即op1必须等op5执行完毕之后才会执行
    // 添加操作依赖,注意不能循环依赖,如果循环依赖会造成两个任务都不会执行
    // 也可以夸队列依赖,依赖别的队列的操作
        [op1 addDependency:op5];
    
    3.2操作监听
    // 监听操作的完成
    // 当op1线程完成之后,立刻就会执行block块中的代码
    // block中的代码与op1不一定在一个线程中执行,但是一定在子线程中执行
    op1.completionBlock = ^{
            NSLog(@"op1已经完成了---%@",[NSThread currentThread]);
        };
    
    3.3NSOperation的一些其他属性和方法
    // 开启线程
    - (void)start;
    - (void)main;
    // 判断线程是否被取消
    @property (readonly, getter=isCancelled) BOOL cancelled;
    // 取消当前线程
    - (void)cancel;
    //NSOperation任务是否在运行
    @property (readonly, getter=isExecuting) BOOL executing;
    //NSOperation任务是否已结束
    @property (readonly, getter=isFinished) BOOL finished;
    // 添加依赖
    - (void)addDependency:(NSOperation *)op;
    // 移除依赖
    - (void)removeDependency:(NSOperation *)op;
    // 优先级
    typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
        NSOperationQueuePriorityVeryLow = -8L,
        NSOperationQueuePriorityLow = -4L,
        NSOperationQueuePriorityNormal = 0,
        NSOperationQueuePriorityHigh = 4,
        NSOperationQueuePriorityVeryHigh = 8
    };
    // 操作监听
    @property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);
    // 阻塞当前线程,直到该NSOperation结束。可用于线程执行顺序的同步
    - (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);
    // 获取线程的优先级
    @property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);
    // 线程名称
    @property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);
    @end
    

    4NSOperationQueue的一些高级操作

    NSOperationQueue的作用
    NSOperation可以调用start方法来执行任务,但默认是同步执行的
    如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

    添加操作到NSOperationQueue中
    - (void)addOperation:(NSOperation *)op;
    - (void)addOperationWithBlock:(void (^)(void))block;

    注意:将操作添加到NSOperationQueue中,就会自动启动,不需要再自己启动了addOperation 内部调用 start方法
    start方法 内部调用 main方法

    4.1NSOperationQueue的重要属性和方法
    4.1.1最大并发数
       - (void)demo {
        /*
         默认是并发队列,如果最大并发数>1,并发
         如果最大并发数==1,串行队列
         系统的默认是最大并发数-1 ,表示不限制
         设置成0则不会执行任何操作
         */
        self.opQueue.maxConcurrentOperationCount = 2;
        //把多个操作放到队列里面
        for (int i = 0; i < 10; i++) {
            NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
                [NSThread sleepForTimeInterval:3.0];
                NSLog(@"%@------%d",[NSThread currentThread],i);
                
            }];
            //把Block操作放到队列
            [self.opQueue addOperation:op];
        }
    }
    
    //重写getter方法实现懒加载
    - (NSOperationQueue*)opQueue {
        if (_opQueue == nil) {
            _opQueue = [[NSOperationQueue alloc]init];
        }
        return _opQueue;
    }
    
    4.1.2线程的挂起
    #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(@"继续");
      }
    }
    
    4.1.3取消队列里的所有操作
    #pragma mark - 高级操作:取消队列里的所有操作
    - (IBAction)cancelAll:(UIButton *)sender {
      //只能取消所有队列的里面的操作,正在执行的无法取消
      //取消操作并不会影响队列的挂起状态
      [self.opQueue cancelAllOperations];
      NSLog(@"取消队列里所有的操作");
      //取消队列的挂起状态
      //(只要是取消了队列的操作,我们就把队列处于不挂起状态,以便于后续的开始)
      self.opQueue.suspended = NO;
    
    }
    

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

    4.1.4依赖关系
     /*
      * 例子
      *
      * 1.下载一个小说压缩包
      *  2.解压缩,删除压缩包
      * 3.更新UI
      */
    
      NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1.下载一个小说压缩包,%@",[NSThread currentThread]);
    
      }];
    
      NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2.解压缩,删除压缩包,%@",[NSThread currentThread]);
    
      }];
    
      NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3.更新UI,%@",[NSThread currentThread]);
    
      }];
    
      //指定任务之间的依赖关系 --依赖关系可以跨队列(可以再子线程下载,在主线程更新UI)
    
      [op2 addDependency:op1];
      [op3 addDependency:op2];
    //  [op1 addDependency:op3];  一定不能出现循环依赖
    
      //waitUntilFinished  类似GCD中的调度组的通知
      //NO不等待,直接执行输出come here
      //YES等待任务执行完再执行输出come here
      [self.opQueue addOperations:@[op1,op2] waitUntilFinished:YES];
    
    
      //在主线程更新UI
      [[NSOperationQueue mainQueue] addOperation:op3];
      [op3 addDependency:op2];
      NSLog(@"come here");
    
    4.2NSOperationQueue的一些其他属性和方法
    // 获取队列中的操作
    @property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
    // 队列中的操作数
    @property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
    // 最大并发数,同一时间最多只能执行三个操作
    @property NSInteger maxConcurrentOperationCount;
    // 暂停 YES:暂停 NO:继续
    @property (getter=isSuspended) BOOL suspended;
    // 取消所有操作
    - (void)cancelAllOperations;
    // 阻塞当前线程直到此队列中的所有任务执行完毕
    - (void)waitUntilAllOperationsAreFinished;
    

    5.NSOperation和NSOperationQueue结合使用创建多线程

        注:这里使用NSBlockOperation示例,其他两种方法一样
        // 1. 创建非主队列 同时具备并发和串行的功能,默认是并发队列
        NSOperationQueue *queue =[[NSOperationQueue alloc]init];
        //NSBlockOperation 不论封装操作还是追加操作都是异步并发执行
        // 2. 封装操作
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"download1 -- %@",[NSThread currentThread]);
        }];
        // 3. 将封装操作加入主队列
        // 也可以不获取封装操作对象 直接添加操作到队列中
        //[queue addOperationWithBlock:^{
        // 操作
        //}];
        [queue addOperation:op1];
    

    6.NSOperation线程之间的通信

    NSOperation线程之间的通信方法

    // 回到主线程刷新UI
    [[NSOperationQueue mainQueue]addOperationWithBlock:^{
          self.imageView.image = image;
    }];
    

    我们同样使用下载多张图片合成综合案例

    #import "ViewController.h"
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    @property(nonatomic,strong)UIImage *image1;
    @property(nonatomic,strong)UIImage *image2;
    @end
    @implementation ViewController
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        // 创建非住队列
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        // 下载第一张图片
        NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{
            NSURL *url = [NSURL URLWithString:@"http://img2.3lian.com/2014/c7/12/d/77.jpg"];
            NSData *data = [NSData dataWithContentsOfURL:url];
            self.image1 = [UIImage imageWithData:data];
        }];
        // 下载第二张图片
        NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
            NSURL *url = [NSURL URLWithString:@"http://img2.3lian.com/2014/c7/12/d/77.jpg"];
            NSData *data = [NSData dataWithContentsOfURL:url];
            self.image2 = [UIImage imageWithData:data];
        }];
        // 合成操作
        NSBlockOperation *combie = [NSBlockOperation blockOperationWithBlock:^{
            // 开启图形上下文
            UIGraphicsBeginImageContext(CGSizeMake(375, 667));
            // 绘制图片1
            [self.image1 drawInRect:CGRectMake(0, 0, 375, 333)];
            // 绘制图片2
            [self.image2 drawInRect:CGRectMake(0, 334, 375, 333)];
            // 获取合成图片
            UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
            // 关闭图形上下文
            UIGraphicsEndImageContext();
            // 回到主线程刷新UI
            [[NSOperationQueue mainQueue]addOperationWithBlock:^{
                self.imageView.image = image;
            }];
        }];
        // 添加依赖,合成图片需要等图片1,图片2都下载完毕之后合成
        [combie addDependency:download1];
        [combie addDependency:download2];
        // 添加操作到队列
        [queue addOperation:download1];
        [queue addOperation:download2];
        [queue addOperation:combie];
    }
    @end
    

    注意:子线程执行完操作之后就会立即释放,即使我们使用强引用引用子线程使子线程不被释放,也不能给子线程再次添加操作,或者再次开启。

    参考文章:
    https://juejin.im/post/5a9e57af6fb9a028df222555
    https://imlifengfeng.github.io/article/533/

    相关文章

      网友评论

        本文标题:iOS底层原理总结 - NSOperation详尽总结

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