美文网首页iOS收藏iOS
iOS多线程之NSOperation、NSOperationQu

iOS多线程之NSOperation、NSOperationQu

作者: 云霄_云霄 | 来源:发表于2019-03-04 17:45 被阅读163次
    • 本作者在写这篇文章之前很详细的写了一篇关于ios中GCD的讲解,这两篇文章可以对比着看,地址为:https://www.jianshu.com/p/94affa011185,也可以去我的文章列表中查找。

    1.什么是NSOperation:

    NSOperation和NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperation和NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。

    2.NSOperation的子类

    NSOperation是一个抽象类,并不具备封装操作的能力,必须使用它的子类,使用NSOperation的子类的方式有三种:

    1. NSInvocationOperation以函数的方式封装任务
    2. NSBlockOperation以Block块的方式封装任务
    3. 自定义子类继承NSOperation,实现内部相应的方法。

    3.NSOperation、NSOperationQueue 操作和操作队列

    既然是基于 GCD 的更高一层的封装。那么,GCD 中的一些概念同样适用于 NSOperation、NSOperationQueue。在 NSOperation、NSOperationQueue 中也有类似的任务(操作)和队列(操作队列)的概念。

    3.1 操作(Operation):
    • 执行操作的意思,换句话说就是你在线程中执行的那段代码。
    • 在 GCD 中是放在 block 中的。在 NSOperation 中,我们使用 NSOperation 子类 NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。
    3.2 操作队列(Operation Queues):
    • 这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
    • 操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发、串行。
    • NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。其中自定义队列默认为并发队列,但是可以通过控制来变成一个串行队列,主队列为串行队列,运行在主线程之上,而自定义队列在后台执行。

    4.NSOperation和NSOperationQueue实现多线程的具体步骤

    1. 先将需要执行的操作封装到一个NSOperation中
    2. 然后将NSOperation对象添加到NSOperationQueue中
    3. 系统会自动将NSOperationQueue中的NSOperation取出来
    4. 将取出来的NSOperation封装的操作放到一条新线程中执行
    //简单添加任务
    /*
     输入结果
     2019-03-04 15:44:05.880348+0800 SmallProgram[4075:178369] 1---<NSThread: 0x600000ece7c0>{number = 1, name = main}
     2019-03-04 15:44:05.880541+0800 SmallProgram[4075:178369] 2---<NSThread: 0x600000ece7c0>{number = 1, name = main}
     2019-03-04 15:44:05.880711+0800 SmallProgram[4075:178369] 3---<NSThread: 0x600000ece7c0>{number = 1, name = main}
     2019-03-04 15:44:05.880736+0800 SmallProgram[4075:178504] 4---<NSThread: 0x600000e68b00>{number = 7, name = (null)}
     2019-03-04 15:44:05.880736+0800 SmallProgram[4075:178482] 5---<NSThread: 0x600000e68140>{number = 8, name = (null)}
     */
    -(void)blockOperation{
        //01 --封装操作对象
        NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
            //任务01
            NSLog(@"1---%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
            //任务02
            NSLog(@"2---%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
            //任务03
            NSLog(@"3---%@",[NSThread currentThread]);
        }];
        //追加任务
        //当一个操作中的任务数量大于1的时候,就会开启子线程和当前线程一起执行任务
        [op3 addExecutionBlock:^{
            NSLog(@"4---%@",[NSThread currentThread]);
        }];
        [op3 addExecutionBlock:^{
            NSLog(@"5---%@",[NSThread currentThread]);
        }];
        //02--执行操作
        [op1 start];
        [op2 start];
        [op3 start];
        
    }
    

    5.操作队列NSOperationQueue的基本使用

    -(void)blockOperationQueue{
        
        //01 --创建队列
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        //02 --封装操作对象
        NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
            //任务01
            NSLog(@"1---%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
            //任务02
            NSLog(@"2---%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
            //任务03
            NSLog(@"3---%@",[NSThread currentThread]);
        }];
        //03 --把操作添加到队列中
        [queue addOperation:op1]; //该方法内部会自动调用start方法执行任务
        [queue addOperation:op2];
        [queue addOperation:op3];
        
        /*
        //简便方法:该方法n首先会吧block中任务封装成一个操作,然后把该操作直接添加到队列中
        [queue addOperationWithBlock:^{
            NSLog(@"4---%@",[NSThread currentThread]);
        }];
        */
    }
    

    6.设置maxConcurrentOperationCount来改为串行队列

    -(void)changeSerialQueue{
        
        //01 --创建队列
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        //02 --封装操作对象
        NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
            //任务01
            NSLog(@"1---%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
            //任务02
            NSLog(@"2---%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
            //任务03
            NSLog(@"3---%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op4 = [NSBlockOperation blockOperationWithBlock:^{
            //任务03
            NSLog(@"4---%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op5 = [NSBlockOperation blockOperationWithBlock:^{
            //任务03
            NSLog(@"5---%@",[NSThread currentThread]);
        }];
        //设置最大并发数--同一时间最多有多少调线程执行
        //maxConcurrentOperationCount ==0 不执行任务
        //maxConcurrentOperationCount默认为-1,指一个最大的值
        queue.maxConcurrentOperationCount = 1;
        //03 --把操作添加到队列中
        [queue addOperation:op1]; //该方法内部会自动调用start方法执行任务
        [queue addOperation:op2];
        [queue addOperation:op3];
        [queue addOperation:op4];
        [queue addOperation:op5];
    
    }
    

    7.操作队列的暂停、回复和取消功能

        //暂停
        //只能暂停当前操作后面的操作,当前操作不可分割必须执行完毕
        [queue setSuspended:YES];
        //恢复
        [queue setSuspended:NO];
        //取消
        //智能取消队列中处理等待状态的操作
        [queue cancelAllOperations];
    

    8.自定义操作

    //.h文件
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface MYNSOperation : NSOperation
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    //.m文件
    #import "MYNSOperation.h"
    
    @implementation MYNSOperation
    
    //重写main方法来告诉自定义的操作任务是什么
    //好处及其使用:比如工程中多次利用到图片下载,就可以将下载功能放到main方法中,以后调用就直接alloc就可以了
    -(void)main{
        if(self.isCancelled){
            return;
        }
        //执行的任务
        for(int i=0;i<5;i++){
            NSLog(@"%d--%@",i,[NSThread currentThread]);
        }
    }
    @end
    
    //使用方法
    NSOperationQueue * queue = [[NSOperationQueue alloc]init];
    MYNSOperation * op = [[MYNSOperation alloc]init];
    [queue addOperation:op]; //内容会调用start--->main
    
    

    9.操作的依赖和监听

    /*
     输出结果:
     2019-03-04 17:14:04.382909+0800 SmallProgram[5396:240448] 4---<NSThread: 0x600001e9c000>{number = 3, name = (null)}
     2019-03-04 17:14:04.383237+0800 SmallProgram[5396:240448] 电影已经下载好了,可以观看了
     2019-03-04 17:14:04.383261+0800 SmallProgram[5396:240470] 3---<NSThread: 0x600001e59b80>{number = 9, name = (null)}
     2019-03-04 17:14:04.383440+0800 SmallProgram[5396:240470] 2---<NSThread: 0x600001e59b80>{number = 9, name = (null)}
     2019-03-04 17:14:04.383712+0800 SmallProgram[5396:240448] 1---<NSThread: 0x600001e9c000>{number = 3, name = (null)}
     2019-03-04 17:14:04.383955+0800 SmallProgram[5396:240448] 5---<NSThread: 0x600001e9c000>{number = 3, name = (null)}
     */
    -(void)relyOn{
        
        //01 --创建队列
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        NSOperationQueue * queue2 = [[NSOperationQueue alloc] init];
        //02 --封装操作对象
        NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
            //任务01
            NSLog(@"1---%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
            //任务02
            NSLog(@"2---%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
            //任务03
            NSLog(@"3---%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op4 = [NSBlockOperation blockOperationWithBlock:^{
            //任务03
            NSLog(@"4---%@",[NSThread currentThread]);
        }];
        NSBlockOperation * op5 = [NSBlockOperation blockOperationWithBlock:^{
            //任务03
            NSLog(@"5---%@",[NSThread currentThread]);
        }];
        
        //监听任务执行完毕
        op4.completionBlock = ^{
            NSLog(@"电影已经下载好了,可以观看了");
        };
        
        //03设置依赖4-->3-->2-->1-->5
        //警告:不能设置循环依赖,否则死锁
        [op5 addDependency:op1];
        [op1 addDependency:op2];
        [op2 addDependency:op3];
        [op3 addDependency:op4];
        
        //04 --把操作添加到队列中
        [queue addOperation:op1]; //该方法内部会自动调用start方法执行任务
        [queue addOperation:op2];
        [queue addOperation:op3];
        [queue addOperation:op4];
        [queue2 addOperation:op5];
    }
    

    10.操作队列实现线程间通信(模仿子线程下载图片,然后主线程刷新UI)

    /*
     输出结果:
     下载中--<NSThread: 0x600001328bc0>{number = 8, name = (null)}
     2019-03-04 17:24:28.905424+0800 SmallProgram[5558:245680] 刷新UI--<NSThread: 0x600001389840>{number = 1, name = main}
     */
    -(void)download{
        //01 --创建队列
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        //02 --封装操作对象
        NSBlockOperation * download = [NSBlockOperation blockOperationWithBlock:^{
            //01--下载
            NSLog(@"下载中--%@",[NSThread currentThread]);
            //02--UI
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                NSLog(@"刷新UI--%@",[NSThread currentThread]);
            }];
            
        }];
        [queue addOperation:download];
    }
    

    11.NSOperation、NSOperationQueue 线程安全(模仿卖火车票)

    - (void)ticketStatusSave {
        
        self.ticketCount = 100; //模拟总共100张票
        self.lock = [[NSLock alloc] init]; //初始化锁
        
        // 01.创建 queue1,模拟窗口1
        NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
        queue1.maxConcurrentOperationCount = 1;
        
        // 02.创建 queue2,模拟窗口2
        NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
        queue2.maxConcurrentOperationCount = 1;
        
        // 03.添加到队列,然后开始卖票
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            [self saleTicketSafe];
        }];
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            [self saleTicketSafe];
        }];
        
        [queue1 addOperation:op1];
        [queue2 addOperation:op2];
    }
    
    /**
     * 售卖火车票(线程安全)
     */
    - (void)saleTicketSafe {
        while (1) {
            
            // 加锁
            [self.lock lock];
            
            if (self.ticketCount > 0) {
                //如果还有票,继续售卖
                self.ticketCount--;
                NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%zd 窗口:%@", self.ticketCount, [NSThread currentThread]]);
                [NSThread sleepForTimeInterval:0.2];
            }
            // 解锁
            [self.lock unlock];
            if (self.ticketCount <= 0) {
                NSLog(@"所有火车票均已售完");
                break;
            }
        }
    }
    

    12.GCD和NSOperation的比较

    12.1 GCD:
    • GCD是纯C语言的API,而操作队列则是Object-C对象。
    • 在GCD中,任务块(Block)来表示,而块是个轻量级的数据结构,相反操作队列中的NSOperation则是个更加重量级的Object-C对象。
    • 具体该使用哪种还得具体情况分析。
    12.2 NSOperation和NSOperationQueue的优势:
    • NSOperation和NSOperationQueue可以方便的调用cancel方法来取消某个操作,而GCD中的任务是无法被取消的(安排好任务之后就不管了)。
    • NSOperation可以方便的指定操作间的依赖关系。
    • NSOperation可以通过KVO提供对NSOperation对象的精细控制(如监听当前操作是否被取消或者是否已经完成)
    • NSOperation可以方便的设置操作的优先级(qualityOfService),操作优先级表示此操作与队列中其它操作之间的优先关系,优先级高德操作先执行。
    • 通过自定义的NSOperation的子类可以实现操作重用。

    结尾

    本文参考:
    iOS 多线程:『NSOperation、NSOperationQueue』详尽总结https://www.jianshu.com/p/4b1d77054b35

    相关文章

      网友评论

        本文标题:iOS多线程之NSOperation、NSOperationQu

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