美文网首页
iOS - 多线程三部曲之NSOperation(三)

iOS - 多线程三部曲之NSOperation(三)

作者: flowerflower | 来源:发表于2019-03-28 21:41 被阅读0次
😀😀

目录
一、基础理论
1.1 NSOperation概念
1.2 为什么要使用 NSOperation、NSOperationQueue?
1.3 万变不离其宗之术语巩固
1.4 NSOperation的使用步骤
二、NSOperation的基本操作
2.1 使用子类NSInvocationOperation
2.2 使用子类NSBlockOperation
2.3 使用NSBlockOperation 的addExecutionBlock操作
2.4 控制最大并发量
2.5 NSOperation操作依赖
2.6 NSOperation、NSOperationQueue线程间的通信
2.7 任务执行状态控制(待续......)
三、结合案例使用
案例1:子线程下载图片->主线程显示图片
案例2:有 A、B 两个任务,要等 A 执行完,B 才能执行
案例3:售票的小故事

一、基础理论

1.1 NSOperation概念

NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更简单易用,代码可读性更高。它使用起来比GCD更灵活,功能也更加强大。NSOperation需要配合NSOperationQueue来实现多线程,因为在默认情况下,NSOperation单独使用时系统同步执行操作,并没有开辟新线程的能力,只有配合NSOperationQueue才能实现异步执行。
NSOperation是个抽象类,并不能封装任务,我们只有使用它的子类(NSInvocationOperation和NSBlockOperation)来封装任务。

1.2 为什么要使用 NSOperation、NSOperationQueue?
  • 可添加完成的代码块,在操作完成后执行。
  • 添加操作之间的依赖关系,方便的控制执行顺序。
  • 支持设置执行的优先级,从而影响 operation 之间的相对执行顺序。
  • 支持取消操作,可以允许我们停止正在执行的 operation 。
  • 使用 KVO 观察对操作执行状态的更改:isExecuteing( 代表任务正在执行中)、isFinished( 代表任务已经执行完成)、isCancelled( 代表任务已经取消执行)。
1.3 万变不离其宗之术语巩固
串行 vs 并发

区别:在于允许同时执行的任务数量
串行: 一次只能执行一个任务,必须等上一个任务执行完成之后才能执行下一个任务
并发:允许多个任务同时执行

同步 vs 异步

区别::在于能否开启新的线程
同步: 在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程中执行任务,具备开启新线程的能力

队列 vs 线程

队列: 指的是串行队列和并发队列
线程: 是否具有开启线程的能力

Operation vs GCD

Operation:相对于GCD来说,更加强大。可以给operation之间添加依赖关系、取消一个正在执行的operation、暂停和恢复operationQueue等

GCD: 是一种更轻量级的,以FIFO(先进先出,后进后出)的顺序执行并发任务。使用GCD我们并不用关心任务的调度情况,而是系统会自动帮我们处理。但是GCD的短板也是非常明显的,比如我们想要给任务之间添加依赖关系、取消或者暂停一个正在执行的任务时就会变得束手无策。

1.4 NSOperation的使用步骤
  • 创建操作:先将需要执行的操作封装到一个NSOperation对象中
  • 创建队列:创建NSOperationQueue对象
  • 将操作加入队列中: 将NSOperation对象添加到NSOperationQueue对象中

综上之后 系统就会自动将NSOperationQueue中的NSOperation取出来,在新线程中执行操作


二、NSOperation的基本操作

NSOperation是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作。

  • 使用子类NSInvocationOperation
  • 使用子类NSBlockOperation
  • 自定义继承自NSOperation的子类, 通过实现内部相应的方法来封装操作

在不使用NSOperationQueue,单独使用NSOperation的情况下系统同步执行操作

2.1 使用子类NSInvocationOperation

  • NSInvocationOperation: 操作是在当前线程执行的,并没有开启新线程。
- (void)userInvocationOperation{
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(texttest) object:nil];
    //调用 start 方法开始执行操作
    [operation start];
}
- (void)texttest{
    NSLog(@"%@",[NSThread currentThread]);  
   //打印结果为: <NSThread: 0x60800007a740>{number = 1, name = main}
}

2.2 使用子类NSBlockOperation

  • NSBlockOperation:操作是在当前线程执行的,并没有开启新线程
- (void)userBolockOperation{
    
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
       //打印结果为: <NSThread: 0x60800006b540>{number = 1, name = main}

    }];
    [operation start];
}

2.3 使用NSBlockOperation 的addExecutionBlock操作

  • blockOperationWithBlock:操作是在主线程执行的,addExecutionBlock方法中的操作是在其他线程中执行的。但当添加的操作的数量较多时,就会自动开启新线程。blockOperationWithBlock:中的操作也可能会在其他线程中执行
- (void)addExecutionBlock{
    NSBlockOperation *p = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1------%@", [NSThread currentThread]);
    }];
        // 添加额外的任务
    [p addExecutionBlock:^{
        NSLog(@"2------%@", [NSThread currentThread]);

        for (int i = 0 ; i < 10000; i++) {
            if (i == 9999){
            NSLog(@"耗时操作");
            }
        }
    }];
    [p addExecutionBlock:^{
        NSLog(@"3------%@", [NSThread currentThread]);
    }];
    [p addExecutionBlock:^{
        NSLog(@"4------%@", [NSThread currentThread]);
    }];
    [p start];
}
图片.png

从打印结果我们可以看出,blockOperationWithBlock:方法中的操作是在主线程中执行的,
而addExecutionBlock:方法中的操作是在其他线程中执行的。

但是请继续往下看

*前方高能

- (void)addExecutionBlock{
    NSBlockOperation *p = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1------%@", [NSThread currentThread]);
        
    }];
        // 添加额外的任务
    [p addExecutionBlock:^{
        NSLog(@"2------%@", [NSThread currentThread]);

        for (int i = 0 ; i < 10000; i++) {
            if (i == 9999){
            NSLog(@"耗时操作");
            }
        }
    }];
    [p addExecutionBlock:^{
        NSLog(@"3------%@", [NSThread currentThread]);
    }];
    [p addExecutionBlock:^{
        NSLog(@"4------%@", [NSThread currentThread]);
    }];
        [p addExecutionBlock:^{
            NSLog(@"5------%@", [NSThread currentThread]);
        }];
    [p start];
}
图片.png

小结
如果添加的操作多的情况下,blockOperationWithBlock:中的操作也可能会在其他线程中执行,这是由系统决定的,并不是说添加到blockOperationWithBlock:中的操作一定是在当前线程中执行。NSBlockOperation是否开启新线程,取决于操作的数量,如果添加的操作数量多的话,就会自动开启新线程。当然开启的线程数是由系统来决定的。

2.4 控制最大并发量

使用NSOperationQueue中的maxConcurrentOperationCount的属性来控制串行执行、并发执行

最大并发操作数(maxConcurrentOperationCount): 用来控制一个特定队列中可以有多少个操作同时参与并发执行。 控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而且一个操作也并非只能在一个线程中运行。

  • maxConcurrentOperationCount默认为-1,表示不进行限制,可进行并发执行。
  • maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
  • maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行。
- (void)setupMaxConcurrentOperationCount{
    
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    //queue.maxConcurrentOperationCount = 1;
    queue.maxConcurrentOperationCount = 2;
    [queue addOperationWithBlock:^{
        
        NSLog(@"1------%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        
        NSLog(@"2------%@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        
        NSLog(@"3------%@", [NSThread currentThread]);
    }];
}
最大并发操作数为1输出结果 最大并发操作数为2输出结果

2.5 NSOperation操作依赖

- (void)addDependency{
    
    //1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    //2.创建操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"执行任务A----%@",[NSThread currentThread]);
    }];

    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"执行任务B----%@",[NSThread currentThread]);
    }];
    //3.添加依赖
    [op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2
    //4.添加操作到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];   
}

2.6 NSOperation、NSOperationQueue线程间的通信

- (void)mainRefreshUI{
    
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    // 2.添加操作
    [queue addOperationWithBlock:^{
        // 异步模拟执行耗时操作
        for (int i = 0; i < 100; i++) {
            NSLog(@"1---%@", [NSThread currentThread]);
        }
        // 回到主线程刷新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            
            NSLog(@"2---%@", [NSThread currentThread]);
        }];
    }];
}
图片.png

2.7 任务执行状态控制

暂未吃透
~~待续

三、结合案例使用

案例1:子线程下载图片->主线程显示图片

  //1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    //2.添加操作
    [queue addOperationWithBlock:^{
       
        NSURL *url = [NSURL URLWithString:@"https://img.haomeiwen.com/i1658521/929b88123cf7156c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"];

        UIImage *image  = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];

        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
          
            self.imgView.image = image;
        }];
    }];

案例2:有 A、B 两个任务,要等 A 执行完,B 才能执行

// 添加依赖 控制执行顺序
 //1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    //2.创建操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
       
        for (int i = 0; i < 1000; i++) {
            if (i == 999) {
                NSLog(@"----模拟添加耗时操作1111----");
            }
        }
        NSLog(@"执行任务A----%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 10; i++) {
          if (i == 9) {
                NSLog(@"----模拟添加耗时操作2222----");
            }
        }
        NSLog(@"执行任务B----%@",[NSThread currentThread]);
    }];
    //3.添加依赖
    [op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2
    //4.添加操作到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];
image.png

案例3:售票的小故事

//声明两个属性
@property(nonatomic,assign)NSInteger ticketSurplusCount; //总票数
@property(nonatomic,strong)NSLock *lock; //用来处理线程安全

//售票的小故事
- (void)buyTicketSafe{
    
    _ticketSurplusCount = 30;

    self.lock = [[NSLock alloc]init];
    
    NSOperationQueue *queue1 = [[NSOperationQueue alloc]init];
    queue1.maxConcurrentOperationCount = 1;
    
    NSOperationQueue *queue2 = [[NSOperationQueue alloc]init];
    queue2.maxConcurrentOperationCount = 1;
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [self saleTicketSafa];
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        
        [self saleTicketSafa];
    }];
    
    //添加操作
    [queue1 addOperation:op1];
    [queue2 addOperation:op2];
    
}

- (void)saleTicketSafa{
    
    
    while (1) {
        
        //加锁
        [self.lock lock];
        
 
        if (self.ticketSurplusCount > 0) {
            
            
            self.ticketSurplusCount --;
            
            NSLog(@"剩余票数:%zd 窗口:%@",self.ticketSurplusCount,[NSThread currentThread]);
        }
        
        //解锁
        [self.lock unlock];
        
        if (self.ticketSurplusCount  <=0) {
            NSLog(@"不好意思。。票买完了。。。。");
            break;      
  }
    }

}

image.png

相关文章

网友评论

      本文标题:iOS - 多线程三部曲之NSOperation(三)

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