【iOS】—— NSOperation、NSOperationQueue

文章目录

  • NSOperation
    • NSOperation、NSOperationQueue 简介
    • 为什么要使用 NSOperation、NSOperationQueue?
    • 操作和操作队列
    • NSOperation、NSOperationQueue常用属性和方法归纳
      • NSOperation常用属性和方法
      • NSOperationQueue常用属性和方法
    • NSOperation、NSOperationQueue的使用
      • 使用步骤
      • 基本使用
        • 创建操作
        • 使用自定义继承自 NSOperation 的子类
    • NSOperationQueue用法
      • 创建队列
      • 将操作加入队列中
      • NSOperationQueue控制串行执行、并行执行
      • NSOperation操作依赖
        • 不添加依赖:
        • 添加依赖:
        • 添加相互依赖
      • NSOperation优先级
      • NSOperation、NSOperationQueue 线程间的通信
      • NSOperation、NSOperationQueue 常用属性和方法归纳
        • NSOperation 常用属性和方法
        • NSOperationQueue 常用属性和方法
      • 多线程的比较
          • 1.pthread
          • 2.GCD与NSOperation
          • 3.GCD 与 NSThread 的区别
        • 4.多线程的优缺点

NSOperation

NSOperation、NSOperationQueue 简介

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

为什么要使用 NSOperation、NSOperationQueue?

  • 可添加完成的代码块,在操作完成后执行。
  • 可以添加操作之间的依赖关系,方便的控制执行顺序。
  • 可以设定操作执行的优先级。
  • 可以很方便的取消一个操作的执行。
  • 使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。

操作和操作队列

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

  • 操作(Operation)
    • 执行操作的意思,就是在线程中执行的那段代码
    • 在GCD中是放在Block中的。在NSOperation中,我们使用NSOperation子类NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作
  • 操作队列(Operation Queues)
    • 操作队列,即用来存放操作的队列。不同于GCD中的调度队列FIFO(先进先出)的原则。NSOperationQueue对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于与操作之间的依赖关系),然后进入就绪状态的这些操作,开始执行的顺序由操作之间的优先级决定。(优先级是操作对象自身的属性)

NSOperation、NSOperationQueue常用属性和方法归纳

NSOperation常用属性和方法

  • 取消操作cancel
  • 判断操作状态方法
    • isFinished判断操作是否已经结束
    • isCancelled判断操作是否取消
    • isExecuting判断操作是否正在运行
    • isReady判断是否处于就绪状态
    • isAsynchronous表示任务是并发还是同步执行
  • 操作同步
    • waitUntilFinished阻塞当前线程
    • setCompletionBlock:(void (^)(void))block;当前操作完毕之后执行block
    • addDependency添加依赖
    • removeDependency移除依赖
    • @property (readonly, copy) NSArray *dependencies;数组存储操作

NSOperationQueue常用属性和方法

  • 取消/暂停/恢复队列中的操作
    • cancelAllOperations;取消队列中所有的操作
    • isSuspended判断队列是否处理暂停状态 YES:暂停状态,NO恢复状态
    • setSuspended:(BOOL)b;设置操作的暂停和恢复 YES:暂停,NO:恢复
  • 同步操作
    • waitUntilAllOperationsAreFinished;阻塞当前线程,直到队列中的操作全部完成
  • 添加/获取
    • addOperationWithBlock:(void (^)(void))block向队列中添加一个NSBlockOperation类型的操作对象
    • addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;添加操作数组,wait标志是否阻塞当前线程知道所有操作结束
    • operations当前在队列中的操作数组
    • operationCount操作个数
  • 获取队列
    • currentQueue当前队列,如果当前线程不在Queue上,返回nil
    • mainQueue获取主队列

暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。

NSOperation、NSOperationQueue的使用

使用步骤

NSOperation实现多线程的使用步骤分为三步:

  • 1.创建操作:先将需要执行的操作封装到一个NSOperation对象中
  • 2.创建队列:创建NSOperationQueue对象
  • 3.将操作加入到队列中:将NSOperation对象添加到NSOperationQueue对象中

之后,系统会自动将NSOperationQueue中的NSOperation取出来,在新线程中执行操作。

基本使用

创建操作

因为NSOperation是个抽象类,不能创建实例,所以我们通常使用它的子类来进行封装操作:

  • 使用子类NSInvocationOperation
  • 使用子类NSBlockOperation
  • 自定义继承自NSOperation的子类,通过实现内部相应的方法来封装操作
    使用子类NSInvocationOperation
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 1.创建 NSInvocationOperation 对象
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testOp) object:nil];
    // 2.调用 start 方法开始执行操作
    [op start];
}
- (void)testOp {
    NSLog(@"testOp--%@", [NSThread currentThread]);
}

输出结果:
在这里插入图片描述

  • 因为没有使用NSOperationQueue,并且我们是在当前线程(主线程)中执行一个操作,所以它是在当前线程(主线程)中完成操作的,并没有开启新线程。
  • 如果在其他线程中执行操作,则打印的结果为其他线程。总之,它是在那个线程创建并启动的,它就会在执行。

使用子类NSBlockOperation

    NSLog(@"----%@", [NSThread currentThread]); // 打印当前线程
    
    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"%d---%@", i, [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 2.调用 start 方法开始执行操作
    [op start];

输出结果:
【iOS】—— NSOperation、NSOperationQueue_第1张图片
可以看到:在没有使用 NSOperationQueue、在主线程中单独使用 NSBlockOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程。

注意: 和上边 NSInvocationOperation 使用一样。因为代码是在主线程中调用的,所以打印结果为主线程。如果在其他线程中执行操作,则打印结果为其他线程。

但是,NSBlockOperation 还提供了一个方法 addExecutionBlock:,通过 addExecutionBlock: 就可以为 NSBlockOperation 添加额外的操作。这些操作(包括 blockOperationWithBlock 中的操作)可以在不同的线程中同时(并发)执行。只有当所有相关的操作已经完成执行时,才视为完成。

如果添加的操作多的话, blockOperationWithBlock: 中的操作也可能会在其他线程(非当前线程)中执行,这是由系统决定的,并不是说添加到 blockOperationWithBlock: 中的操作一定会在当前线程中执行。(可以使用 addExecutionBlock: 多添加几个操作试试):

- (void)useBlockOperationAddExecutionBlock {

    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 2.添加额外的操作
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"5---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"6---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"7---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"8---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 3.调用 start 方法开始执行操作
    [op start];
}

运行结果:
【iOS】—— NSOperation、NSOperationQueue_第2张图片
可以看出:使用子类 NSBlockOperation,并调用方法 AddExecutionBlock: 的情况下,blockOperationWithBlock:方法中的操作 和 addExecutionBlock: 中的操作是在不同的线程中异步执行的。而且,这次执行结果中 blockOperationWithBlock:方法中的操作也不是在当前线程(主线程)中执行的。从而印证了 blockOperationWithBlock: 中的操作也可能会在其他线程(非当前线程)中执行。

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

使用自定义继承自 NSOperation 的子类

如果使用子类 NSInvocationOperation、NSBlockOperation 不能满足日常需求,我们可以使用自定义继承自 NSOperation 的子类。可以通过重写 main 或者 start 方法 来定义自己的 NSOperation 对象。重写main方法比较简单,我们不需要管理操作的状态属性 isExecuting 和 isFinished。当 main 执行完返回的时候,这个操作就结束了

先定义一个继承自 NSOperation 的子类,重写main方法。

//
//  MYOperation.m
//  NSOperation学习
//
//  Created by 翟旭博 on 2023/4/5.
//

#import "MYOperation.h"

@implementation MYOperation
- (void)main {
    if (!self.isCancelled) {
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@", [NSThread currentThread]);
        }
    }
}
@end

我们运行下康康:
在这里插入图片描述
可以看出:在没有使用 NSOperationQueue、在主线程单独使用自定义继承自 NSOperation 的子类的情况下,是在主线程执行操作,并没有开启新线程。

另外我们再尝试一下start方法的重写:

- (void)start {
    if (!self.isCancelled) {
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@", [NSThread currentThread]);
        }
    }
    [super start];
}

运行结果:
在这里插入图片描述
效果同上。

NSOperationQueue用法

创建队列

NSOperationQueue共有两种队列:主队列、自定义队列,其中自定义队列同时包含了串行、并发功能。

  • 主队列:凡是添加到主队列中的操作,都会放到主线程中执行。
  • 自定义队列(非主队列):添加到这种队列中的操作,会自动放到子线程中执行,同时包含了 串行、并发 功能。
// 主队列获取方法
NSOperationQueue *queue = [NSOperationQueue mainQueue];

// 自定义队列(非主队列)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

将操作加入队列中

NSOperation需要配合NSOperationQueue才能实现多线程,将创建好的操作加入到队列中,有两种方法:

1.- (void)addOperation:(NSOperation *)op;方法

创建好队列、操作,再将操作都加到队列中:

    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.创建操作
    NSInvocationOperation *firstOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationFirst) object:nil];
    NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2---%@", [NSThread currentThread]);
    }];
    [secondOperation addExecutionBlock:^{
        NSLog(@"add--%@", [NSThread currentThread]);
    }];
    NSInvocationOperation *thirdOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationThird) object:nil];

    // 3.将操作加到队列中
    [queue addOperation:firstOperation];
    [queue addOperation:secondOperation];
    [queue addOperation:thirdOperation];


.......

- (void)operationFirst {
    NSLog(@"1---%@", [NSThread currentThread]);
}
- (void)operationThird {
    NSLog(@"3---%@", [NSThread currentThread]);
}

【iOS】—— NSOperation、NSOperationQueue_第3张图片
我们可以看到,添加到队列中的操作开启了新线程来完成。

2.- (void)addOperationWithBlock:(void (^)(void)block);
不用创建操作,直接在block中添加操作,将block加入到队列中:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1---%@", [NSThread currentThread]);
    }];

    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2---%@", [NSThread currentThread]);
    }];

    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3---%@", [NSThread currentThread]);
    }];

输出结果:
在这里插入图片描述
与上述方法相同,不过使用block看起来更简洁了。

NSOperationQueue控制串行执行、并行执行

操作队列有一个属性,最大并发操作数,用来控制一个特定的队列中可以有多少个操作同时并发执行,也就是一个队列中同时能并发执行的最大操作数:

@property NSInteger maxConcurrentOperationCount;
  • maxConcurrentOperationCount默认为-1,表示不进行限制,可进行并发执行
  • maxConcurrentOperationCount为1时,此时队列为串行队列,只能串行执行
  • maxConcurrentOperationCount大于1时,队列为并发队列。操作并发执行。当这个值超过了系统限制,就会自动调整为系统设定的值。
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    queue.maxConcurrentOperationCount = 2;  // 控制一次最多执行的线程数
    
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1---%@", [NSThread currentThread]);
    }];

    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2---%@", [NSThread currentThread]);
    }];

    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3---%@", [NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"4---%@", [NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"5---%@", [NSThread currentThread]);
    }];

输出结果:

queue.maxConcurrentOperationCount = 1;

在这里插入图片描述

queue.maxConcurrentOperationCount = 2;

在这里插入图片描述

queue.maxConcurrentOperationCount = -1;

在这里插入图片描述

可能从输出结果上看不出什么,主要是输出的过程,你可以自己用上述代码试试,当为 1 的时候,明显能看出来每个操作之间都有2秒的延时,但是为 2 的时候,它延迟两秒之后几乎同时输出的。

对于开启线程数,是由系统决定的,不需要我们来管理。

NSOperation操作依赖

NSOperation、NSOperationQueue最吸引人的就是它能够添加操作之间的依赖关系,通过依赖关系,我们就可以很方便的控制操作之间的执行先后顺序。
NSOperation提供了3个接口供我们使用依赖

  • -(void)addDependency:(NSOperation *)op添加依赖,是当前操作依赖op的完成,op完成之后才会执行当前操作。
  • -(void)removeDependency:(NSOperation *)op移除依赖,取消当前操作对操作op的依赖。
  • @property (readonly, copy) NSArray *dependencies;操作对象的一个属性,在当前操作开始执行之前完成执行的所有操作对象数组。

不添加依赖:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"firstOperation");
}];
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"secondOperation");
}];
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"thirdOperation");
}];

[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];

输出结果:

在这里插入图片描述
没有添加依赖,顺序是随机的。

添加依赖:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"firstOperation");
}];
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"secondOperation");
}];
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"thirdOperation");
}];

[secondOperation addDependency:firstOperation]; // 让secondOperation依赖于firstOperation,即firstOperation先执行,在执行secondOperation
[thirdOperation addDependency:secondOperation]; // 让thirdOperation依赖于secondOperation,即secondOperation先执行,在执行thirdOperation

[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];

输出结果:
在这里插入图片描述

添加相互依赖

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"firstOperation");
}];
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"secondOperation");
}];
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"thirdOperation");
}];

[secondOperation addDependency:firstOperation]; // 让secondOperation依赖于firstOperation,即firstOperation先执行,在执行secondOperation
[firstOperation addDependency:secondOperation]; // 让firstOperation依赖于secondOperation,即secondOperation先执行,在执行firstOperation

[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];

输出结果:
在这里插入图片描述

两个相互依赖之后,都不输出了

NSOperation优先级

依赖只是一种执行关系罢了,NSOperation还为我们专门提供了优先级属性,我们可以通过改变NSOperationQueuePriority属性来设置同一队列中操作的优先级,下面是系统给定的优先级(默认为NSOperationQueuePriorityNormal):

// 优先级的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
}

对于添加到队列中的操作,首先进入准备就绪的状态,然后进入就绪状态的操作的开始执行顺序由操作之间的相对的优先级决定。

就绪状态取决于操作之间的依赖关系,也就是只有这个操作的依赖操作完成了,该操作才会处于就绪状态。

举个例子:
当有四个优先级都是NSOperationQueuePriorityNormal(默认优先级)的操作:op1、op2、op3、op4,op2依赖于op3,op3依赖于op4。

  • 其中只有op1、op4没有需要依赖的操作,所以op1、op4就是处于准备就绪状态的操作。
  • op2、op3都有依赖的操作,所以op2、op3都不是准备就绪的操作。
  • 当op4完成时,op3进入就绪状态;当op3完成时,op2进入就绪状态。

queuePriority:

  • queuePriority属性决定了已进入就绪状态下的操作之间的开始执行顺序。优先级不能取代依赖关系,该属性仅决定开始执行顺序,并不能保证完成执行顺序。
  • 如果一个队列中既包含高优先级操作、也有低优先级操作,并且这两个操作都已经准备就绪,那么队列就会先执行高优先级操作。
  • queuePriority属性决定的是进入就绪状态下的操作之间的开始执行顺序,并不保证执行完成顺序。而依赖则是控制两个操作之间的执行顺序,使一个操作在它依赖的操作执行完成之后再开始执行。

NSOperation、NSOperationQueue 线程间的通信

在 iOS 开发过程中,我们一般在主线程里边进行 UI 刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

    UIImageView *imageView = [[UIImageView alloc] init];
    imageView.frame = CGRectMake(100, 100, 200, 200);
    imageView.backgroundColor = [UIColor orangeColor];
    [self.view addSubview:imageView];
    //1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //2.添加操作
    [queue addOperationWithBlock:^{
        // 1. 获取图片 imageUrl
        NSURL *imageUrl = [NSURL URLWithString:@"https://img-blog.csdnimg.cn/d317e3af47424e03bea4572b6fa4b917.png"];
        // 2. 从 imageUrl 中读取数据(下载图片) -- 耗时操作
        NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
        // 通过二进制 data 创建 image
        UIImage *image = [UIImage imageWithData:imageData];
        NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        
        // 回到主线程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [imageView setImage:image];   //UI操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }];
    }];

输出结果:
在这里插入图片描述

NSOperation、NSOperationQueue 常用属性和方法归纳

NSOperation 常用属性和方法

1.取消操作方法

  • - (void)cancel; 可取消操作,实质是标记 isCancelled 状态

2.判断操作状态方法

  • - (BOOL)isFinished; 判断操作是否已经结束
  • - (BOOL)isCancelled; 判断操作是否已经标记为取消
  • - (BOOL)isExecuting; 判断操作是否正在在运行
  • - (BOOL)isReady; 判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关

3.操作同步

  • - (void)waitUntilFinished; 阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步
  • - (void)setCompletionBlock:(void (^)(void))block; 会在当前操作执行完毕时执行 completionBlock
  • - (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成
  • - (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖
  • @property (readonly, copy) NSArray *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组

NSOperationQueue 常用属性和方法

1.取消/暂停/恢复操作

  • - (void)cancelAllOperations; 可以取消队列的所有操作
  • - (BOOL)isSuspended; 判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态
  • - (void)setSuspended:(BOOL)b; 可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列

2.操作同步

  • - (void)waitUntilAllOperationsAreFinished; 阻塞当前线程,直到队列中的操作全部执行完毕

3.添加/获取操作

  • - (void)addOperationWithBlock:(void (^)(void))block; 向队列中添加一个 NSBlockOperation 类型操作对象
  • - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; 向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
  • - (NSArray *)operations; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)
  • - (NSUInteger)operationCount; 当前队列中的操作数

4.获取队列

  • + (id)currentQueue; 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil
  • + (id)mainQueue; 获取主队列

注意:

  • 这里的暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
  • 暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。

多线程的比较

关于多线程四种方法的比较,我们再来看看这张老图:
【iOS】—— NSOperation、NSOperationQueue_第4张图片

1.pthread

pthread跨平台,使用难度大,需要手动管理线程生命周期

2.GCD与NSOperation
  • GCD的执行效率更高,执行的是由Block构成的任务,是一个轻量级的数据结构,写起来更加方便
  • GCD只支持FIFO队列,NSOperation可以通过设置最大并发数、设置优先级、添加依赖关系来调整执行顺序
  • NSOperation可以跨越队列设置依赖关系,GCD仅仅能通过栅栏等方法才能控制执行顺序
  • NSOperation更加面向对象,支持KVO,也可以通过继承等关系添加子类。

所以如果我们需要考虑异步操作之间的顺序行、依赖关系,比如多线程并发下载等等,就使用NSOperation。

3.GCD 与 NSThread 的区别
  • NSThread 通过 @selector 指定要执行的方法,代码分散, 依靠的是NSObject的分类实现的线程之间的通讯,如果要开线程必须创建多个线程对象。经常只用的是[NSTread currentThread] 查看当前的线程。
  • NSThread是一个控制线程执行的对象,它不如NSOperation抽象,通过它我们可以方便的得到一个线程,并控制它。但NSThread的线程之间的并发控制,是需要我们自己来控制的,可以通过NSCondition实现。
  • GCD 通过 block 指定要执行的代码,代码集中, 所有的代码写在一起的,让代码更加简单,易于阅读和维护,不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期

4.多线程的优缺点

  • 优点
    • 使应用程序的响应速度更快,用户界面在进行其他工作的同时仍始终保持活动状态;
    • 优化任务执行,适当提高资源利用率(CPU,内存);
  • 缺点
    • 线程占用内存空间,管理线程需要额外CPU开销,开启大量线程,降低程序性能;
    • 增加程序复杂度,如线程间通信,多线程的资源共享等等;

你可能感兴趣的:(ios,objective-c,swift)