深入浅出iOS多线程(四)——NSOperation多线程

深入浅出iOS多线程(一)——线程的概念
深入浅出iOS多线程(二)——pthraed和NSThread的使用
深入浅出iOS多线程(三)——GCD多线程
深入浅出iOS多线程(四)——NSOperation多线程
深入浅出iOS多线程(五)——多线程锁

NSOperation的作用

简介

和GCD一样,NSOperation也是并发编程技术,NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,比GCD更加简单,更加方便

NSoperation需要配合NSOperationQueue来实现多线程,NSOperation单独使用时系统同步执行操作,并没有开辟新的线程的能力,只有配合NSoperationQueue才能实现异步执行。

NSOperation是苹果大力推荐的"并发"技术
NSOperation的核心概念是,将"操作"添加进"队列"
GCD将"任务"添加到"队列"

  • NSOperation是一个抽象类
    • 特点:不能直接使用
    • 目的:定义子类共有的属性和方法
    • 子类:NSInvocationOperation、NSBlockOperation

由于NSOperation是基于GCD的,使用步骤也和GCD差不多,其中,NSOperation相当于GCD中的任务,而NSOperationQueue则相当于GCD中的队列,NSOperation实现多线程的使用步骤分3步:

  • 创建操作:创建NSOperation子类对象
  • 创建队列:创建NSOperationQueue队列
  • 将操作添加到队列中去:NSOperation添加到NSOperationQueue中去

系统会自动取出"队列"中的"操作"执行。

NSOperation多线程的基本使用

NSInvocationOperation的基本使用

  • 将NSOperation添加到队列,会自动异步执行调度方法

    NSInvocationOperation * op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"invocation"];
    NSOperationQueue * q = [[NSOperationQueue alloc]init];
    [q addOperation:op];
    
  • 打印结果

    {number = 3, name = (null)}  invocation
    
  • 将NSOperation添加到队列,会自动异步执行调度方法

根据上述代码我们可以确信NSOperationQueue是一个队列,而NSInvocationOperation是一个操作(任务),那么这个队列到底是什么样的队列,而这个操作是什么样的操作,在上述代码中队列添加更多的"操作",代码如下:

//队列
NSOperationQueue * q = [[NSOperationQueue alloc]init];
    
for (int i = 0; i<10; i++) {
    NSInvocationOperation * op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@(i)];
    //将操作添加到队列 - 会自动异步执行调度方法
    [q addOperation:op];
}

打印结果:

{number = 4, name = (null)}  1
{number = 3, name = (null)}  0
{number = 6, name = (null)}  3
{number = 5, name = (null)}  2
{number = 7, name = (null)}  4
{number = 3, name = (null)}  8
{number = 5, name = (null)}  6
{number = 6, name = (null)}  7
{number = 4, name = (null)}  5
{number = 7, name = (null)}  9

从打印结果上面看,线程不一样,执行顺序也不一样,说明这个队列是并发队列,队列中的操作是异步操作。

NSBlockOperation的基本使用

NSBlockOperationNSInvocationOperation使用步骤几乎一摸一样,不一样的地方在于,NSBlockOperationNSInvocationOperation省略了一个@selector,代码更加简洁。


/**
    第一种block形式
**/
//1.队列
NSOperationQueue * q = [[NSOperationQueue alloc]init];
    
//2.操作
for (int i = 0; i<10; i++) {
    NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
         [NSThread currentThread].name = [@(i) stringValue];
        NSLog(@"%@",[NSThread currentThread]);
    }];
    //将操作添加到队列 - 会自动异步执行调度方法
    [q addOperation:op];
}

/**
    第一种block形式
**/
//1.队列
NSOperationQueue * q = [[NSOperationQueue alloc]init];
    
//2.操作
for (int i = 0; i<10; i++) {
    //将操作添加到队列 - 会自动异步执行调度方法
    [q addOperationWithBlock:^{
         [NSThread currentThread].name = [@(i) stringValue];
        NSLog(@"%@",[NSThread currentThread]);
    }];
}

打印结果:

{number = 5, name = 1}
{number = 6, name = 3}
{number = 3, name = 0}
{number = 4, name = 2}
{number = 3, name = 5}
{number = 6, name = 4}
{number = 5, name = 6}
{number = 4, name = 7}
{number = 7, name = 8}
{number = 3, name = 9}

上述代码运行结果,同样可以知道这个队列是并发队列,队列中的操作是异步操作。

上述代码,所有的代码都要添加一个队列怎么办?

  • self中定义一个类属性,在配合懒加载的形式,这样的话不管这个类中哪个方法有添加队列的操作,那么都可以使用这一个"队列",这样会让代码更加简洁。
@interface ViewController ()
@property (nonatomic,strong)NSOperationQueue * MyQueue;
@end

- (NSOperationQueue *)MyQueue{
    
    if(!_MyQueue){
        _MyQueue = [[NSOperationQueue alloc]init];
    }
    return _MyQueue;
}

for (int i = 0; i<10; i++) {
    //将操作添加到队列 - 会自动异步执行调度方法
    [self.MyQueue addOperationWithBlock:^{
        [NSThread currentThread].name = [@(i) stringValue];
        NSLog(@"%@",[NSThread currentThread]);
    }];
}

很简洁很爽

NSOperation线程间的通信

//小清新
[self.MyQueue addOperationWithBlock:^{
    NSLog(@"耗时操作");
    [[NSOperationQueue mainQueue]addOperationWithBlock:^{
        NSLog(@"更新UI");
    }];
}];

NSOperation最大并发数

  • 从 iOS 8.0 开始,无论使用 GCD还是 NSOperation ,都会开启很多线程
  • 在 iOS 7.0 以前,GCD 通常只会开启 5 6条线程!
  • 目前线程多了说明:
    1. 底层的现场池更大了,能够拿到的线程资源多了!
    2. 多控制同时并发的现场数,要求就更高了!
//添加操作进队列
/*
 从 iOS 8.0 开始,无论使用 GCD还是 NSOperation ,都会开启很多线程
 在 iOS 7.0 以前,GCD 通常只会开启 5  6条线程!
 目前线程多了说明:
 1.底层的现场池更大了,能够拿到的线程资源多了!
 2.多控制同时并发的现场数,要求就更高了!
 */
    
for (int i = 0;i < 20; i++) {
    [self.MyQueue addOperationWithBlock:^{
        NSLog(@"%@---%d",[NSThread currentThread],i);
    }];
}

打印结果:

{number = 4, name = (null)}---1
{number = 5, name = (null)}---2
{number = 6, name = (null)}---3
{number = 3, name = (null)}---0
{number = 4, name = (null)}---4
{number = 6, name = (null)}---5
{number = 5, name = (null)}---6
{number = 3, name = (null)}---7
{number = 4, name = (null)}---8
{number = 6, name = (null)}---9
{number = 5, name = (null)}---10
{number = 7, name = (null)}---11
{number = 8, name = (null)}---12
{number = 9, name = (null)}---13
{number = 3, name = (null)}---14
{number = 4, name = (null)}---15
{number = 5, name = (null)}---16
{number = 10, name = (null)}---17
{number = 11, name = (null)}---18
{number = 6, name = (null)}---19
  • 通常设置同时最大的并发操作数量

    • WIFI: 5 至 6
    • 流量 : 2 到 3
    self.MyQueue.maxConcurrentOperationCount = 2;
    
    for (int i = 0;i < 20; i++) {
        [self.MyQueue addOperationWithBlock:^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"%@---%d",[NSThread currentThread],i);
        }];
    }
    

打印结果:

{number = 4, name = (null)}---0
{number = 3, name = (null)}---1
{number = 6, name = (null)}---2
{number = 5, name = (null)}---3
{number = 5, name = (null)}---5
{number = 3, name = (null)}---4
{number = 3, name = (null)}---7
{number = 6, name = (null)}---6
{number = 6, name = (null)}---9
{number = 4, name = (null)}---8
{number = 6, name = (null)}---10
{number = 4, name = (null)}---11
{number = 6, name = (null)}---13
{number = 3, name = (null)}---12
{number = 3, name = (null)}---15
{number = 4, name = (null)}---14
{number = 5, name = (null)}---16
{number = 3, name = (null)}---17
{number = 3, name = (null)}---19
{number = 4, name = (null)}---18

为什么实际当中打印会多出几个线程?

  1. 线程中的任务完成以后会回收线程
  2. 当一个任务完成需要从队列中取新的任务,取一个空间的线程,或者是开辟新线程的时候,而1中的线程正在回收,所以这个任务开辟的线程会比原先的线程number+1

NSOperationQueue的属性和方法

suspended

  • NSOperationQueue的一个属性,可以控制NSOperationQueue队列的挂起还是继续

    • isSuspended 判断是否是挂起
    • suspended 修改NSOperationQueue挂起或者继续
    //队列是否挂起
    if(self.MyQueue.isSuspended){
        
        NSLog(@"继续");
        self.MyQueue.suspended = NO;
        
    }else{
        NSLog(@"暂停");
        self.MyQueue.suspended = YES;
    }
    
    -(void)demo1{
        self.MyQueue.maxConcurrentOperationCount = 2;
            
        for (int i = 0;i < 20; i++) {
            [self.MyQueue addOperationWithBlock:^{
                [NSThread sleepForTimeInterval:1.0];
                NSLog(@"%@---%d",[NSThread currentThread],i);
            }];
        }
    }
    

operationCount

可以拿到队列中的操作数

NSLog(@"%tu",self.MyQueue.operationCount);

取消NSOperationQueue中的所有操作

注意:

  1. 队列挂起的时候,取消队列所有操作,不会清空队列的operationCoount只有在队列继续的时候才能清空
  2. 正在执行的操作也不会被清空,也不会被取消
[self.MyQueue cancelAllOperations];

NSOperation依赖关系(Dependency)

waitUntilFinished中的YES会卡住当前线程

[self.MyQueue addOperations:@[op1,op2,op3] waitUntilFinished:YES];

NSOperation依赖关系:

- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;

例子:

/*
 *  例子:下载/解压/通知用户
 **/
    
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下载----%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"解压----%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"通知用户----%@",[NSThread currentThread]);
}];
    
//NSOperation 提供了依赖关系
//注意:不要指定循环依赖,队列就不工作了,不会造成死锁。
[op2 addDependency:op1];
[op3 addDependency:op2];
    
//YES 会卡住当前线程
[self.MyQueue addOperations:@[op1,op2,op3] waitUntilFinished:YES];
NSLog(@"come here  %@",[NSThread currentThread]);
    
    //主线程通知用户
[[NSOperationQueue mainQueue]addOperation:op3];
    

打印结果:

下载----{number = 3, name = (null)}
解压----{number = 4, name = (null)}
通知用户----{number = 3, name = (null)}
come here  {number = 1, name = main}

总结

  • 只要是NSOperation的子类,就可以添加到NSOperationQueue
  • 如果使用NSOperation可以使代码更加简洁,代码会更加工整,NSOperation能办到的事情,GCD也都可以办到。
  • NSOperation相比GCD节约来代码行数,并且是面向对象的。
  • NSOperation是苹果大力推荐的并发技术。
  • NSOperation是一个抽象类
    • 特点:不能直接使用
    • 目的:定义子类共有的属性和方法
    • 子类:NSInvocationOperation、NSBlockOperation

GCD 和 NSOperation 对比

GCD 在 iOS 4.0 推出,主要针对多核处理器做了优化的并发技术,是C语言的

  • 将"任务"[block]添加到 队列[串行/并发/主队列/全局队列] ,并且指定执行任务的函数[同步/异步]
  • 线程间的通讯 dispatch_get_main_queue()
  • 提供了一些 NSOperation 不具备的功能
    • 一次执行
    • 延迟执行
    • 调度组(在op中也可以做到,有点麻烦)
    • 信号量
    • apply(重复)

NSOperation 在 iOS 2.0 推出的,苹果推出 GCD以后,对NSOperation 底层做了重写!

  • 将操作[异步执行的任务] 添加到队列[并发队列],就会立刻异步执行
  • mainQueue
  • 提供了一些GCD 实现起来比较困难的功能
  • 最大并发线程
  • 队列的暂停/继续
  • 取消所有操作
  • 指定操作之间的依赖关系(GCD 用同步来实现)

多线程NSOperation结构图

深入浅出iOS多线程(四)——NSOperation多线程_第1张图片
NSOperation.png

你可能感兴趣的:(深入浅出iOS多线程(四)——NSOperation多线程)