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执行完毕,他才执行。

关于优先级与依赖关系:

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

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

今天就先玩到这里把。

posted @ 2019-04-08 20:13  呼啸而过  阅读(272)  评论(0编辑  收藏  举报