• IOS高级开发之多线程(四)NSOperation


    1.什么是NSOperation,NSOperationQueue?

    NSOperation是一个抽象的基类,表示一个独立的计算单元,可以为子类提供有用且线程安全的建立状态,优先级,依赖和取消等操作。系统已经给我们封装了NSBlckOperation和NSInvocationOperation两个实体类。使用起来也非常简单,不过我们更多的使用是自己继承并定制自己的操作。

    2.我们为什么使用NSOperation?

    在IOS的开发中,为了照顾用户体验,我们通常将那些耗时的操作的放到主线程以为的线程去处理。对于一些简单的操作,我们可以使用代码更为简洁的GCD来进行处理。NSOperation也是基于GCD来实现,不过相对于GCD来说,可操控性更强,而且可以加入操作依赖。

    1.可添加完成的代码块,在操作完成后执行。

    2.添加操作之间的依赖关系,方便的控制执行顺序。

    3.设定操作执行的优先级。

    4.可以很方便的取消一个操作的执行。

    5.使用KVO观察对操作状态进行的更改,isExcuting,isFinished,isCancelld.

    NSOperation:执行操作的意思,就是需要在线程中执行的那段代码。

    在GCD中,我们是放到block中去执行的,而在NSOperaion中,我们是使用NSOpration的子类NSInvocationOperation,NSBlokOperation,或者自定义子类来进行封装。

    OperationQueue:操作队列,不同于GCD的先进先出,对于进入队列中的操作,首先进入准备就绪状态,不过这个取决于各个操作之间的依赖关系,然后进入就绪状态的操作的执行顺序,是由操作之间的相对优先级决定的。

    操作队列通过设置最大并发数来控制并发,串行。

    NSOperationQueue为我们提供了两种队列:主队列(运行在主线程),自定义队列(运行在后台)。

    有关NSOperation、NSOperationQueue的使用:

    NSOperation需要配合NSOperationQueue来实现多线程操作。默认情况下,单独使用NSOPeration,系统同步执行操作。配合NSOperationQueue来实现异步操作。

    NSOperation的使用一般分为三个步骤:

    1.创建操作:将需要执行的操作,封装到一个NSOperation中去。

    2.创建队列:创建NSOperationQueue对象。

    3.将操作加入到队列中去:将NSOperation对象加入到NSOperationQueue队列中去。

    在之后,系统会自动将NSOpeationQueue中的NSOperation对象取出来,在新线程中执行。

    我们来具体看下这三步是如何操作的:

    先来看第一步,创建操作:

    NSOperation是一个抽象类,所以我们要使用他的子类来进行封装操作。

    有这么几个方式:

    1.使用NSInvocationOperation

    2.使用NSBlockOperation

    3.自定义继承NSOpeation的子类,通过实现内部相应的方法来封装操作。

    如果不配合NSOperationQueue单独使用NSOperation,系统同步执行操作。

    我们来分别看下代码:

    /*
     使用子类NSInvocationOperation
     */
    -(void)useInvocationOperation{
        //1.创建NSInvocationOperation对象
        NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
        //2.调用start方法执行操作
        [op start];
    }
    
    -(void)task1{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"------%@",[NSThread currentThread]);
    }
    

      打印:

    2019-04-08 17:18:36.020622+0800 MyNSOperation[1118:14940] ------<NSThread: 0x600000fcf900>{number = 1, name = main}
    

      可以看出,操作在当前线程中进行,并没有开启新线程。

    但是,如果在其他线程中执行操作,打印则为其他线程:

    [NSThread detachNewThreadSelector:@selector(useInvocationOperation) toTarget:self withObject:nil];
    

      打印:

    2019-04-08 17:25:38.360812+0800 MyNSOperation[1379:19754] ------<NSThread: 0x6000003cd9c0>{number = 3, name = (null)}
    

      

    接下来,再来看NSBlockOperation:

    /**
     使用子类NSBlockOperation
    */
    -(void)useNSBlockOperation{
        //1.创建NSBlockOperation对象
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"----%@",[NSThread currentThread]);
            }
        }];
        
        //2.调用start方法执行操作
        [op start];
    }
    

      打印结果:

    2019-04-08 17:32:50.691864+0800 MyNSOperation[1669:25050] ----<NSThread: 0x600002f9f0c0>{number = 1, name = main}
    2019-04-08 17:32:52.692550+0800 MyNSOperation[1669:25050] ----<NSThread: 0x600002f9f0c0>{number = 1, name = main}
    

      可以看到NSBlock执行一个操作,是在当前线程中执行的,并没有开启一个新的线程。

    不过也和NSInovationOperation一样,如果在其他线程中执行,则打印结果为其他线程。

    不过NSBlockOperation还提供了一个方法:NSExecutionBlock:,通过这个方法就可以为NSExecutionBlock添加额外的操作。如果添加的操作多的话,blockOperationWithBlock:也可能会在其他线程中执行,这个是由系统决定的。来看下代码:

    /**
     使用NSBlockOperation
     调用AddExecutionBlock;
     */
    -(void)useBlockOperationAddExecutionBlock{
        //1.创建NSBlockOperation对象
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"1-----%@",[NSThread currentThread]);
            }
        }];
        //2.添加额外的操作
        [op addExecutionBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"2-----%@",[NSThread currentThread]);
            }
        }];
        
        [op addExecutionBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"3-----%@",[NSThread currentThread]);
            }
        }];
        [op addExecutionBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"4-----%@",[NSThread currentThread]);
            }
        }];
        [op addExecutionBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"5-----%@",[NSThread currentThread]);
            }
        }];
        [op addExecutionBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"6-----%@",[NSThread currentThread]);
            }
        }];
        [op addExecutionBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"7-----%@",[NSThread currentThread]);
            }
        }];
        [op addExecutionBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"8-----%@",[NSThread currentThread]);
            }
        }];
        [op start];
    }
    

      打印结果:

    2019-04-08 18:43:48.694212+0800 MyNSOperation[3981:56480] 1-----<NSThread: 0x6000003e6840>{number = 1, name = main}
    2019-04-08 18:43:48.694212+0800 MyNSOperation[3981:56536] 3-----<NSThread: 0x600000382e00>{number = 5, name = (null)}
    2019-04-08 18:43:48.694216+0800 MyNSOperation[3981:56538] 2-----<NSThread: 0x600000382d40>{number = 4, name = (null)}
    2019-04-08 18:43:48.694235+0800 MyNSOperation[3981:56537] 4-----<NSThread: 0x6000003bd2c0>{number = 3, name = (null)}
    2019-04-08 18:43:50.695591+0800 MyNSOperation[3981:56480] 1-----<NSThread: 0x6000003e6840>{number = 1, name = main}
    2019-04-08 18:43:50.695591+0800 MyNSOperation[3981:56537] 4-----<NSThread: 0x6000003bd2c0>{number = 3, name = (null)}
    2019-04-08 18:43:50.695592+0800 MyNSOperation[3981:56536] 3-----<NSThread: 0x600000382e00>{number = 5, name = (null)}
    2019-04-08 18:43:50.695639+0800 MyNSOperation[3981:56538] 2-----<NSThread: 0x600000382d40>{number = 4, name = (null)}
    2019-04-08 18:43:52.697095+0800 MyNSOperation[3981:56480] 7-----<NSThread: 0x6000003e6840>{number = 1, name = main}
    2019-04-08 18:43:52.697095+0800 MyNSOperation[3981:56536] 6-----<NSThread: 0x600000382e00>{number = 5, name = (null)}
    2019-04-08 18:43:52.697095+0800 MyNSOperation[3981:56537] 5-----<NSThread: 0x6000003bd2c0>{number = 3, name = (null)}
    2019-04-08 18:43:52.697178+0800 MyNSOperation[3981:56538] 8-----<NSThread: 0x600000382d40>{number = 4, name = (null)}
    2019-04-08 18:43:54.698766+0800 MyNSOperation[3981:56537] 5-----<NSThread: 0x6000003bd2c0>{number = 3, name = (null)}
    2019-04-08 18:43:54.698777+0800 MyNSOperation[3981:56480] 7-----<NSThread: 0x6000003e6840>{number = 1, name = main}
    2019-04-08 18:43:54.698777+0800 MyNSOperation[3981:56536] 6-----<NSThread: 0x600000382e00>{number = 5, name = (null)}
    2019-04-08 18:43:54.698847+0800 MyNSOperation[3981:56538] 8-----<NSThread: 0x600000382d40>{number = 4, name = (null)}
    

      可以看出,在添加了多个AddExcutionBlock操作之后,操作在不同的线程中异步执行了。这个是由系统决定的。

    一般情况下,如果一个NSBlockOperation封装了多个操作,NSBlockOperation是否开启新线程,取决于操作的个数,如果添加的操作个数多,就会开启新线程。当然开启的新的线程数量也是由系统决定的。

    不过我们经常遇到使用NSInvocationOperation以及NSBlockOperation无法满足的情况,这个时候我们就需要自定义NSOpration的子类啦!

    可以通过重写main方法或者star方法来定义自己的NSOperaion对象。

    我们先来看重写main方法:

    新建一个类继承自NSOperation:

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface TestOperation : NSOperation
    
    @end
    
    NS_ASSUME_NONNULL_END
    

      

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface TestOperation : NSOperation
    
    @end
    
    NS_ASSUME_NONNULL_END
    

      在使用的地方:

    //使用自定义的NSOperation
    -(void)useCustomOperaion{
        //1.创建TestOperation对象
        TestOperation *op = [[TestOperation alloc] init];
        //2.调用start方法执行操作
        [op start];
    }
    

      来看打印:

    2019-04-08 19:04:03.931693+0800 MyNSOperation[4724:68006] 1-----<NSThread: 0x600001d113c0>{number = 1, name = main}
    2019-04-08 19:04:05.933372+0800 MyNSOperation[4724:68006] 1-----<NSThread: 0x600001d113c0>{number = 1, name = main}
    

      可以看出,在单独使用NSOperaion的时候,是在当前线程里的,接着我们看下使用NSOperaionQueue:

    NSOperation主要包括两种队列:主队列和自定义队列。

    凡是添加到主队列中的操作都会放到当前线程中执行。(不包括前面提到的addExcutionBlock添加的额外操作);

    主队列的获取方法:

    NSOperationQueue *[mainQueue = [NSOperationQueue mainQueue];
    

     自定义队列:

    添加到自定义队列中的操作就会放到子线程中去执行。同时包含了串行,并发的功能。

    主队列是获取,而自定义队列则需要自己创建了:

    //自定义队列的创建方法
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    

      有了队列,把操作加到队列中就OK了,我们来看下代码:

    //把操作添加到队列中去:
    -(void)addOperationToQueue{
        //1.创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        //2.创建操作
        NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
        NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];
        //使用NSBlockOperation创建操作3
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"3--%@",[NSThread currentThread]);
            }
            
        }];
        [op3 addExecutionBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"4----%@",[NSThread currentThread]);
            }
        }];
        //3.将操作都添加到队列中去
        [queue addOperation:op1];//[op1 start]
        [queue addOperation:op2];
        [queue addOperation:op3];
    }
    

      来看打印:

    2019-04-08 19:26:06.403243+0800 MyNSOperation[5573:82196] ------<NSThread: 0x6000028a0b80>{number = 3, name = (null)}
    2019-04-08 19:26:06.403369+0800 MyNSOperation[5573:82195] 4----<NSThread: 0x6000028a7bc0>{number = 5, name = (null)}
    2019-04-08 19:26:06.403372+0800 MyNSOperation[5573:82198] 3--<NSThread: 0x6000028a0bc0>{number = 4, name = (null)}
    2019-04-08 19:26:08.406961+0800 MyNSOperation[5573:82198] 3--<NSThread: 0x6000028a0bc0>{number = 4, name = (null)}
    2019-04-08 19:26:08.406952+0800 MyNSOperation[5573:82195] 4----<NSThread: 0x6000028a7bc0>{number = 5, name = (null)}
    

      可以看到,加入到了不同的子线程中进行了异步执行。

    再来看一种无需创建操作,直接写在block中的方法,使用NSOPerationWithBlock:

    -(void)addOperationToQueueWithBlock{
        //1.创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        //2.使用addOperaionWithBlock添加操作到队列中
        [queue addOperationWithBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:1.0];
                NSLog(@"1----%@",[NSThread currentThread]);
            }
        }];
        [queue addOperationWithBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:1.0];
                NSLog(@"2----%@",[NSThread currentThread]);
            }
        }];
        [queue addOperationWithBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:1.0];
                NSLog(@"3----%@",[NSThread currentThread]);
            }
     
    

      打印结果:

    2019-04-08 19:41:56.635903+0800 MyNSOperation[6125:93195] 4----<NSThread: 0x6000035d1fc0>{number = 5, name = (null)}
    2019-04-08 19:41:56.635918+0800 MyNSOperation[6125:93186] ------<NSThread: 0x6000035eae00>{number = 3, name = (null)}
    2019-04-08 19:41:56.635904+0800 MyNSOperation[6125:93188] 3--<NSThread: 0x6000035eae40>{number = 4, name = (null)}
    2019-04-08 19:41:58.637956+0800 MyNSOperation[6125:93188] 3--<NSThread: 0x6000035eae40>{number = 4, name = (null)}
    2019-04-08 19:41:58.637958+0800 MyNSOperation[6125:93195] 4----<NSThread: 0x6000035d1fc0>{number = 5, name = (null)}
    

      再来看使用NSOperationQueue来控制串行,并行

    这里注意一个重要属性,maxConcurrentOperationCount,它是一个队列中能同时并发执行的最大操作数量。

    这个数默认情况为-1,表示不进行限制。

    当为1的时候,很显然就是串行。

    大于1的时候,就是并行。当然,你如果设置了很大,超过了系统 限制,系统就自动调整为他所允许的最大值。

    //设置最大并发数量
    -(void)setMaxConcurrentOperationCount{
        //1.创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        //2.设置最大的并发数量
        queue.maxConcurrentOperationCount = 1;//串行队列
    //    queue.maxConcurrentOperationCount = 2;//并发队列
    //    queue.maxConcurrentOperationCount = 999;
        //3.添加操作
        [queue addOperationWithBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"1---%@",[NSThread currentThread]);
            }
        }];
        [queue addOperationWithBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"2---%@",[NSThread currentThread]);
            }
        }];
        [queue addOperationWithBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"3---%@",[NSThread currentThread]);
            }
        }];
        [queue addOperationWithBlock:^{
            for (int i=0; i<2; ++i) {
                [NSThread sleepForTimeInterval:2.0];
                NSLog(@"4---%@",[NSThread currentThread]);
            }
        }];
    }
    

      当设为1的时候的打印:

    2019-04-08 19:52:04.936432+0800 MyNSOperation[6517:99856] 1---<NSThread: 0x6000003b5040>{number = 3, name = (null)}
    2019-04-08 19:52:06.938095+0800 MyNSOperation[6517:99856] 1---<NSThread: 0x6000003b5040>{number = 3, name = (null)}
    2019-04-08 19:52:08.939725+0800 MyNSOperation[6517:99855] 2---<NSThread: 0x6000003b4fc0>{number = 4, name = (null)}
    2019-04-08 19:52:10.940218+0800 MyNSOperation[6517:99855] 2---<NSThread: 0x6000003b4fc0>{number = 4, name = (null)}
    2019-04-08 19:52:12.941507+0800 MyNSOperation[6517:99856] 3---<NSThread: 0x6000003b5040>{number = 3, name = (null)}
    2019-04-08 19:52:14.946991+0800 MyNSOperation[6517:99856] 3---<NSThread: 0x6000003b5040>{number = 3, name = (null)}
    2019-04-08 19:52:16.951288+0800 MyNSOperation[6517:99855] 4---<NSThread: 0x6000003b4fc0>{number = 4, name = (null)}
    2019-04-08 19:52:18.956847+0800 MyNSOperation[6517:99855] 4---<NSThread: 0x6000003b4fc0>{number = 4, name = (null)}
    

      可以看出,为串行执行。

    当设为2的时候的打印:

    2019-04-08 19:53:48.113132+0800 MyNSOperation[6584:101034] 1---<NSThread: 0x60000357bc80>{number = 3, name = (null)}
    2019-04-08 19:53:48.113146+0800 MyNSOperation[6584:101036] 2---<NSThread: 0x60000357bcc0>{number = 4, name = (null)}
    2019-04-08 19:53:50.114523+0800 MyNSOperation[6584:101034] 1---<NSThread: 0x60000357bc80>{number = 3, name = (null)}
    2019-04-08 19:53:50.114535+0800 MyNSOperation[6584:101036] 2---<NSThread: 0x60000357bcc0>{number = 4, name = (null)}
    2019-04-08 19:53:52.116719+0800 MyNSOperation[6584:101035] 3---<NSThread: 0x600003542000>{number = 6, name = (null)}
    2019-04-08 19:53:52.116730+0800 MyNSOperation[6584:101037] 4---<NSThread: 0x600003542880>{number = 5, name = (null)}
    2019-04-08 19:53:54.117248+0800 MyNSOperation[6584:101037] 4---<NSThread: 0x600003542880>{number = 5, name = (null)}
    2019-04-08 19:53:54.117292+0800 MyNSOperation[6584:101035] 3---<NSThread: 0x600003542000>{number = 6, name = (null)}
    

      可以看出,是并发执行。并发的数量为2,但是具体开几个线程,这个就是由系统决定的。我们无需操心。

    接下来,我们就要看NSOperation最令人着迷的地方:操作依赖,通过给操作添加依赖关系,我们就很容易控制操作之间的执行先后顺序。

    NSOperation提供了3个接口供我们来管理和查看依赖关系:

    1.addDependency添加依赖,使当前的操作依赖于参数的那个操作

    2.removeDependency移除依赖

    3.@property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。

    我们来看一个添加依赖操作的代码把:

    //添加依赖:
    -(void)addDependency{
        //1.创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        //2.创建操作
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
            }
        }];
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
            }
        }];
        //3.添加依赖
        [op2 addDependency:op1];
        //4.加到队列中去
        [queue addOperation:op1];
        [queue addOperation:op2];
    }
    

      来看打印:

    2019-04-08 20:07:31.661222+0800 MyNSOperation[7082:109038] 1---<NSThread: 0x60000016bd40>{number = 3, name = (null)}
    2019-04-08 20:07:33.662707+0800 MyNSOperation[7082:109038] 1---<NSThread: 0x60000016bd40>{number = 3, name = (null)}
    2019-04-08 20:07:35.668239+0800 MyNSOperation[7082:109039] 2---<NSThread: 0x600000157900>{number = 4, name = (null)}
    2019-04-08 20:07:37.673734+0800 MyNSOperation[7082:109039] 2---<NSThread: 0x600000157900>{number = 4, name = (null)}
    

      可以看到,无论执行几次,2都会等1执行完毕,他才执行。

    关于优先级与依赖关系:

    优先级不能取代依赖关系。

    优先级作用在同一队列的准备就绪状态下的依赖关系。

    今天就先玩到这里把。

  • 相关阅读:
    SkylineGlobe 如何实现绘制圆形Polygon和对图层的圆形范围选择查询
    如何修改Oracle服务IP地址
    TerraGate软件安装后,不能启动的解决办法
    SkylineGlobe SFS发布的WFS和WMS服务测试
    SkylineGlobe 如何实现二次开发加载KML文件
    如何屏蔽SkylineGlobe提供的三维地图控件上的快捷键
    SkylineGlobe 如何实现FlyTo定位到目标点之后触发的事件函数
    转子百度知道:LTE上行采用DC子载波,而下行置零,为什么?
    转载:LTE小区搜索过程
    dB dBc dBi dBd dBm dBW 定义
  • 原文地址:https://www.cnblogs.com/howlaa/p/10647864.html
Copyright © 2020-2023  润新知