iOS多线程--NSOperation
1. NSOperation简介
NSOperation是苹果提供给我们的一套自带线程管理的抽象类。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更简单易用、代码可读性也更高。
1)优点:自带线程周期管理,操作上可更注重自己逻辑。
2)缺点:面向对象的抽象类,只能实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。
NSOperation需要配合NSOperationQueue来实现多线程。因为默认情况下,NSOperation单独使用时系统同步执行操作,并没有开辟新线程的能力,只有配合NSOperationQueue才能实现异步执行。
因为NSOperation是基于GCD的,那么使用起来也和GCD差不多,其中,NSOperation相当于GCD中的任务,而NSOperationQueue则相当于GCD中的队列。
我们为什么使用NSOperation?
在iOS开发中,为了提升用户体验,我们通常会将操作耗时的操作放在主线程之外的线程进行处理。对于正常的简单操作,我们更多的是选择代码更少的GCD,让我们专注于自己的业务逻辑开发。NSOperation在ios4后也基于GCD实现,但是相对于GCD来说可控性更强,并且可以加入操作依赖。
使用NSOperation实现多线程的三个核心步骤:
1.创建任务:先将需要执行的操作封装到一个NSOperation对象中。
2.创建队列:创建NSOperationQueue对象。
3.将任务加入到队列中:然后将NSOperation对象添加到NSOperationQueue中。
之后呢,系统就会自动将NSOperationQueue中的NSOperation取出来,在新线程中执行操作。
2.核心步骤的实现
2.1 创建任务 --- 核心1
在前面简介上我们提到过NSOperation是个抽象类,并不能封装任务。我们只有使用它的子类来封装任务。我们有三种方式来封装任务。
1.使用子类NSInvocationOperation
2.使用子类NSBlockOperation
3.定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。
在不使用操作队列NSOperationQueue,而单独使用NSOperation的情况下。系统只会同步执行操作,下面我们学习一下这三种创建方式。
2.1.1 使用子类NSInvocationOperation ----创建任务
代码实现如下:
//MARK: 使用子类- NSInvocationOperation
- (void)useInvocationOperation {
NSInvocationOperation *invocationOp = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(runLog) object:nil];
// 调用start方法开始执行操作
[invocationOp start];
}
runLog方法是一个共用的打印方法,如下: 接下来的示例依然会用到它
- (void)runLog {
NSLog(@"1------%@", [NSThread currentThread]);
}
输出控制台信息:
2017-08-31 16:10:39.847 Multi-thread_NSOperation[44199:2115590] 1------<NSThread: 0x60000007e980>{number = 1, name = main}
由以上信息我们可以2.1.1中得到:
单独使用NSInvocationOperation的情况下,NSInvocationOperation在主线程执行操作,并没有开启新线程。
下边再来看看NSBlockOperation
2.1.2 使用子类NSBlockOperation ----创建任务
代码实现如下:
//MARK: 使用子类- NSBlockOperation
- (void)useBlockOperation {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 在主线程
[self runLog];
}];
[op start];
}
输出控制台信息:
2017-08-31 16:24:06.021 Multi-thread_NSOperation[44263:2120051] 1------<NSThread: 0x61800006b6c0>{number = 1, name = main}
由以上信息我们可以从2.1.2中得到:
单独使用NSBlockOperation的情况下,NSBlockOperation也是在主线程执行操作,并没有开启新线程。
但是,NSBlockOperation还提供了一个方法addExecutionBlock:,通过addExecutionBlock:就可以为NSBlockOperation添加额外的操作,这些额外的操作就会在其他线程并发执行,下面让我们一起来看下:
使用addExecutionBlock方法
//MARK: 使用 addExecutionBlock
- (void)useAddExecutionBlock {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 在主线程
[self runLog];
}];
// 添加额外的任务
// 注意:只要NSBlockOperation封装的操作数 >1,就会异步执行操作
[op addExecutionBlock:^{
NSLog(@"2------%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"3------%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"4------%@", [NSThread currentThread]);
}];
[op start];
}
输出控制台信息:
2017-08-31 16:27:17.913 Multi-thread_NSOperation[44298:2121607] 1------<NSThread: 0x608000066500>{number = 1, name = main}
2017-08-31 16:27:17.913 Multi-thread_NSOperation[44298:2121646] 4------<NSThread: 0x6180000673c0>{number = 4, name = (null)}
2017-08-31 16:27:17.913 Multi-thread_NSOperation[44298:2121649] 2------<NSThread: 0x600000068ac0>{number = 3, name = (null)}
2017-08-31 16:27:17.913 Multi-thread_NSOperation[44298:2121647] 3------<NSThread: 0x600000068b80>{number = 5, name = (null)}
由上面的信息我们可以看出,blockOperationWithBlock:方法中的操作是在主线程中执行的,而addExecutionBlock:方法中的操作是在其他线程中执行的,并且只要NSBlockOperation封装的操作数(添加额外的任务) >1,就会异步执行操作。
2.1.3 使用定义继承自NSOperation的子类 (MyTestOperation) ----创建任务
先定义一个继承自NSOperation的子类,我们把它命名为MyTestOperation。重写main方法,代码内容如下:
MyTestOperation.h
#import <Foundation/Foundation.h>
@interface MyTestOperation : NSOperation
@end
MyTestOperation.m
/**
* 需要执行的任务 重写main方法
*/
- (void)main
{
for (int i = 0; i < 5; ++i) {
NSLog(@"%d-----%@",i,[NSThread currentThread]);
}
}
//MARK: 使用定义继承自NSOperation的子类 MyTestOperation
-(void)myTestOperation{
MyTestOperation *op = [[MyTestOperation alloc]init];
[op start];
}
控制台输出:
2017-08-31 16:38:01.040 Multi-thread_NSOperation[44414:2126385] 0-----<NSThread: 0x608000074480>{number = 1, name = main}
2017-08-31 16:38:01.040 Multi-thread_NSOperation[44414:2126385] 1-----<NSThread: 0x608000074480>{number = 1, name = main}
2017-08-31 16:38:01.040 Multi-thread_NSOperation[44414:2126385] 2-----<NSThread: 0x608000074480>{number = 1, name = main}
2017-08-31 16:38:01.040 Multi-thread_NSOperation[44414:2126385] 3-----<NSThread: 0x608000074480>{number = 1, name = main}
2017-08-31 16:38:01.040 Multi-thread_NSOperation[44414:2126385] 4-----<NSThread: 0x608000074480>{number = 1, name = main}
由以上信息我们可以从2.1.2中得到:
单独使用自定义子类的情况下,是在主线程执行操作,并没有开启新线程。
下边我们再看看操作队列NSOperationQueue的创建
2.2 创建队列(NSOperationQueue)--- 核心2
NSOperationQueue的作用
- NSOperation可以调用start方法来执行任务,但默认是同步执行;
- 如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作。
这里值得一提的是和GCD中的并发队列、串行队列略有不同的是:NSOperationQueue可以分为两种队列:
1. 主队列(mainQueue);
2. 其他队列: 其中其他队列同时包含了串行、并发功能。下边是主队列、其他队列的基本创建方法和特点。
2.2.1 主队列
凡是添加到主队列中的任务(NSOperation),都会放到主线程中执行
// 主队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
2.2.2 其他队列
添加到👇这种队列中的任务(NSOperation),就会自动放到子线程中执行
同时包含了:串行、并发功能
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
2.3 将任务(NSOperation)加入到队列(NSOperationQueue)中 --- 核心3
上面提到过NSOperation需要配合NSOperationQueue来实现多线程。即理解为任务需要让队列来管理。
那么我们需要将创建好的任务加入到队列中去。总共有两种方法:
/* 两种方法
-(void)addOperation:(NSOperation*)op;// 1
-(void)addOperationWithBlock:(void(^)(void))block;// 2
*/
这两种方法的
区别是:
1.方法1需要先创建任务,再将创建好的任务加入到创建好的队列中去。
2.方法2无需先创建任务,在block中添加任务,直接将任务block加入到队列中。
相同点:
addOperationWithBlock: 和 NSOperationQueue 都能够开启新线程,进行并发执行。
两种方式的代码实现如下:
- (void)operationQueue{
/* 两种方法
-(void)addOperation:(NSOperation*)op;
-(void)addOperationWithBlock:(void(^)(void))block;
*/
//1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2.创建操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
[self runLog];
}];
//3.添加操作到队列中
[queue addOperation:op];
// 方法二
//添加操作到队列中
[queue addOperationWithBlock:^{
[self runLog];
}];
}
3. NSOperation的高级操作
高级操作中主要包含的是使用NSOperation过程中的一些特性,让我们能够更加了解它。
3.1 NSOperation操作依赖
NSOperation可以添加操作依赖:保证操作的执行顺序!
#pragma mark - NSOperation高级操作 -- NSOperation操作依赖
- (void)highLevelTest {
NSInvocationOperation *inO = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(runLog) object:nil];
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block1======%@", [NSThread currentThread]);
}];
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block2======%@", [NSThread currentThread]);
}];
NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block3======%@", [NSThread currentThread]);
}];
/**
四个操作都是耗时操作,并且要求按顺序执行,操作2是UI操作
添加操作依赖的注意点
1.一定要在将操作添加到操作队列中之前添加操作依赖
2.不要添加循环依赖
优点:对于不同操作队列中的操作,操作依赖依然有效
提示:任务添加的顺序并不能够决定执行顺序,执行的顺序取决于依赖。使用Operation的目的就是为了让开发人员不再关心线程
*/
// 1.一定要在将操作添加到操作队列中之前添加操作依赖
[block2 addDependency:block1];
[block3 addDependency:block2];
[inO addDependency:block3];
// 2.不要添加循环依赖 解开注释即循环依赖了
// [block1 addDependency:block3];
[[[NSOperationQueue alloc] init] addOperation:block1];
[[[NSOperationQueue alloc] init] addOperation:block2];
[[[NSOperationQueue alloc] init] addOperation:block3];
[[NSOperationQueue mainQueue] addOperation:inO];
}
通过上面的给任务(Operation)添加操作依赖,可以让我们的任务按我们设计预想的顺序执行。
3.2 任务(NSOperation)暂停/恢复 取消 操作
应用场景:提高用户体验第一,当用户操作时,取消一切跟用户当前操作无关的进程,提升流畅度
如在tableview界面,开线程下载远程的网络界面,对UI会有影响,使用户体验变差。那么这种情况,就可以设置在用户操作UI(如滚动屏幕)的时候,暂停队列(不是取消队列),停止滚动的时候,恢复队列。
1.添加操作依赖
2.管理操作:重点!是操作队列的方法
3.暂停/恢复 取消 操作
4.开启合适的线程数量!(最多不超过6条)
完成此类问题用到的实现代码如下:
#pragma mark - NSOperation高级操作2
- (void)highLevelTest2 {
/**
NSOperation高级操作
一般开发的时候,会将操作队列(queue)设置成一个全局的变量(属性)
*/
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"---------");
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
[self runLog];
}];
[queue addOperation:block1];
/*
暂停和恢复的适用场合:如在tableview界面,开线程下载远程的网络界面,对UI会有影响,使用户体验变差。那么这种情况,就可以设置在用户操作UI(如滚动屏幕)的时候,暂停队列(不是取消队列),停止滚动的时候,恢复队列。
*/
// 1.暂停操作 开始滚动的时候
[queue setSuspended:YES];
// 2.恢复操作 滑动结束的时候
[queue setSuspended:NO];
// 3.取消所有操作 接收到内存警告
[queue cancelAllOperations];
// 3.1补充:取消单个操作调用该NSOperation的cancel方法
[block1 cancel];
// 4.设置线程最大并发数,开启合适的线程数量 实例化操作队列的时候
[queue setMaxConcurrentOperationCount:6];
/**
遇到并发编程,什么时候选择 GCD, 什么时候选择NSOperation
1.简单的开启线程/回到主线程,选择GCD:效率更高,简单
2.需要管理操作(考虑到用户交互!)使用NSOperation
*/
}
3.3 NSOperation实现线程间通信
NSOperation实现线程间通信:
1)利用代理进行消息传递
2)利用通知实现消息传递
3)利用block进行消息传递
你可能还要示例源码 在这里 源码
您可能还对 iOS多线程--GCD 感兴趣
您可能还对 iOS多线程--NSOperation 感兴趣
您可能还对 iOS多线程--NSThread 感兴趣