iOS知识点iOS - 多线程iOS 多线程

iOS多线程--NSOperation

2017-08-31  本文已影响103人  9d8c8692519b

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的作用

  1. NSOperation可以调用start方法来执行任务,但默认是同步执行
  2. 如果将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 感兴趣

上一篇下一篇

猜你喜欢

热点阅读