iOS 多线程-NSOperation + NSOperationQueue

本文内容:
如何使用NSOperation创建任务、设置依赖、设置优先级
如何使用NSOperationQueue创建队列,添加任务到队列、控制队列串行、并行执行任务
线程间如何通信
如何保证线程安全
iOS多线程demo地址

上文说到iOS 多线程-GCD
这篇文章来讲讲NSOperation + NSOperationQueue

NSOperation:基于OC语言的API,底层是GCD,增加了一些简单易用的功能,面向对象操作,线程的生命周期系统自动管理,在开发中经常使用。

1. NSOperation & NSOperationQueue

NSOperation:执行的操作,也就是任务的意思,不过NSOperation:是一个抽象基类,不能直接使用。

NSOperation两种使用方式:

  1. 使用系统定义的NSInvocationOperation
  2. 使用系统定义的NSBlockOperation
  3. 自定义类继承NSOperation

任务的状态分为以下几种:

  1. ready : 就绪
  2. cancelled : 取消 (只能取消没有开始的任务)
  3. executing : 正在执行
  4. finished : 完成
  5. asynchronous : 并发还是非并发

NSOperationQueue: 存放任务的队列。
队列分为:

  1. 主队列:添加到主队列的任务,只能在主线程执行,除addExecutionBlock添加的额外任务,任务可能在其他线程执行(下面有证明);
  2. 其他队列:添加到其他队列的任务在子线程执行。

常用方法:

NSOperation使用addDependency添加依赖, 控制任务是否进入就绪状态,从而控制任务执行顺序。
NSOperationQueue使用addOperation添加任务到队列。
NSOperationQueue设置 maxConcurrentOperationCount(最大操作并发数),用来控制一个队列中有多少个任务同时进行,而不是并发线程的数量。
NSOperation使用cancel 取消当前任务
NSOperationQueue使用cancelAllOperations取消当前队列的任务
NSOperationQueue使用setSuspended设置任务的暂停和恢复

注意:

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

2.使用步骤

  1. 创建任务
  2. 创建队列
  3. 将任务添加到队列中执行

2.1创建任务

2.1.1 NSInvocationOperation

使用NSInvocationOperation创建任务,使用start方式开始任务

- (void)clickNSOperation{
    NSLog(@"主线程");
    
//    1、NSInvocationOperation
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationAction) object:nil];
    [invocationOperation start];
    NSLog(@"end----");
}
- (void)invocationAction{

    for (NSInteger index = 0; index < 3; index ++) {
        NSLog(@"invocation ==== %ld",(long)index);
        [NSThread sleepForTimeInterval:1.0];
    }
}

输出结果:

  1. 在主线程中执行(因为是在主线程中调用)
  2. end最后输出,start方式执行任务是同步执行,睡眠时阻塞当前线程。
2019-01-03 15:59:44.044995+0800 Thread[5045:65704] 主线程
2019-01-03 15:59:44.045577+0800 Thread[5045:65704] invocation ==== 0
2019-01-03 15:59:45.046159+0800 Thread[5045:65704] invocation ==== 1
2019-01-03 15:59:46.047736+0800 Thread[5045:65704] invocation ==== 2
2019-01-03 15:59:47.048575+0800 Thread[5045:65704] end----


在主线程调用就在主线程执行,那在子线程中调用会如何呢?

- (void)clickNSOperation{
    NSLog(@"主线程");
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //    1、NSInvocationOperation
        NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationAction) object:nil];
        //start 是同步方式
        [invocationOperation start];
        NSLog(@"end----");
    });
}

输出结果:

  1. 任务在子线程中执行(因为是子主线程中调用)
  2. end最后输出,start方式执行任务是同步执行,睡眠时阻塞当前线程。
2019-01-03 16:01:59.252994+0800 Thread[5075:66813] 主线程
2019-01-03 16:01:59.254415+0800 Thread[5075:66861] invocation ==== 0
2019-01-03 16:02:00.257107+0800 Thread[5075:66861] invocation ==== 1
2019-01-03 16:02:01.259739+0800 Thread[5075:66861] invocation ==== 2
2019-01-03 16:02:02.261961+0800 Thread[5075:66861] end----

由此可见,使用NSInvocationOperation + start方式执行任务,在哪个线程调用,就在哪个线程执行,执行方式是同步执行。

2.1.2NSBlockOperation

使用NSBlockOperation创建一个任务,并使用start方式开始任务

- (void)clickNSOperation{
    NSLog(@"主线程");
    
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger index = 0; index < 3; index ++) {
            NSLog(@"1 ==== %ld",(long)index);
            [NSThread sleepForTimeInterval:1.0];
        }
    }];
     [blockOperation start];
     NSLog(@"end----");
}

输出结果:

  1. 在主线程中执行(因为是在主线程中调用)
  2. end最后输出,start方式执行任务是同步执行,睡眠时阻塞当前线程。
2019-01-03 16:41:28.661599+0800 Thread[5506:85345] 主线程
2019-01-03 16:41:28.662055+0800 Thread[5506:85345] 1 ==== 0
2019-01-03 16:41:29.663436+0800 Thread[5506:85345] 1 ==== 1
2019-01-03 16:41:30.664878+0800 Thread[5506:85345] 1 ==== 2
2019-01-03 16:41:31.666310+0800 Thread[5506:85345] end----

执行一个任务时,省略在子线程调用start执行任务,因为结果和NSInvocationOperation一样在哪个线程调用,就在哪个线程同步执行。

NSBlockOperation还有一个方法addExecutionBlock用来添加额外任务,可用于执行多个任务,比如说想同时执行2个任务。

- (void)clickNSOperation{
    NSLog(@"主线程");
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger index = 0; index < 3; index ++) {
            NSLog(@"1 ==== %ld",(long)index);
            [NSThread sleepForTimeInterval:1.0];
        }
    }];
    [blockOperation addExecutionBlock:^{
        for (NSInteger index = 0; index < 3; index ++) {
            NSLog(@"2 ==== %ld",(long)index);
            [NSThread sleepForTimeInterval:1.0];
        }
    }];

    [blockOperation start];
    NSLog(@"end----");

}

输出结果:

  1. 任务1在主线程执行,任务2在子线程执行。由此证明,addExecutionBlock添加的任务,可能在其他子线程执行。
  2. end最后输出,start方式执行任务是同步执行,睡眠时阻塞当前线程。
2019-01-03 16:47:02.219309+0800 Thread[5552:87869] 主线程
2019-01-03 16:47:02.219847+0800 Thread[5552:87896] 1 ==== 0
2019-01-03 16:47:02.219870+0800 Thread[5552:87869] 2 ==== 0
2019-01-03 16:47:03.221204+0800 Thread[5552:87896] 1 ==== 1
2019-01-03 16:47:03.221214+0800 Thread[5552:87869] 2 ==== 1
2019-01-03 16:47:04.222522+0800 Thread[5552:87896] 1 ==== 2
2019-01-03 16:47:04.222522+0800 Thread[5552:87869] 2 ==== 2
2019-01-03 16:47:05.223986+0800 Thread[5552:87869] end----

使用addExecutionBlock方式执行多个任务时,会开启线程,具体开启几个线程是由系统决定的。

2.1.3 自定义类继承NSOperation

创建一个类继承自NSOperation,这里创建的是XXOperation

#import "XXOperation.h"

@interface XXOperation ()
@property (nonatomic , copy) NSString *operName;
@property (nonatomic , assign) BOOL over;

@end

@implementation XXOperation

- (instancetype)initWithName:(NSString *)operName{
    if (self = [super init]) {
        self.operName = operName;
    }
    return self;
}

- (void)main{
    for (NSInteger index = 0; index < 3; index ++) {
        NSLog(@"index ==== %d,operName = %@",index,self.operName);
        [NSThread sleepForTimeInterval:1.0];
    }
  }

使用start方式调用

- (void)clickNSOperation{
    NSLog(@"主线程");
    XXOperation *operationA = [[XXOperation alloc]initWithName:@"operationA"];
    [operationA start];
  NSLog(@"end---");
}
 
    

输出结果:

  1. 在主线程中执行(因为是在主线程中调用)
  2. end最后输出,start方式执行任务是同步执行,睡眠时阻塞当前线程。
2019-01-03 16:20:16.400576+0800 Thread[5270:75970] 主线程
2019-01-03 16:20:16.401009+0800 Thread[5270:75970] index ==== 0,operName = operationA
2019-01-03 16:20:17.401740+0800 Thread[5270:75970] index ==== 1,operName = operationA
2019-01-03 16:20:18.402675+0800 Thread[5270:75970] index ==== 2,operName = operationA
2019-01-03 16:20:19.404857+0800 Thread[5270:75970] end---

自定义类继承NSOperation在子线程中调用任务,这里就省略了,
因为结果和NSInvocationOperationNSBlockOperation执行一个任务时一样,在哪个线程调用,就在哪个线程同步执行任务。

但是我们常见的需求中是异步并发执行,如何实现异步并发执行呢?我们需要使用NSOperation + NSOperationQueue实现异步并发。

队列又分为两种,添加到主队列的任务,只能在主线程执行,除addExecutionBlock添加的额外任务,可能在其他线程执行。添加到其他队列的任务在子线程执行。

由此可见,使用 NSOperation + 其他队列是较佳方法,既不阻塞主线程,又能执行其他任务。

2.2创建队列

2.2.1主队列

//获取主队列
   NSOperationQueue *queue = [NSOperationQueue mainQueue];

2.2.2其他队列

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

3.添加任务到队列

  1. addOperation:(NSOperation *)op: 直接添加一个 NSOperation操作
  2. addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait : 添加一组操作,wait标志是否阻塞当前线程直到所有操作结束
  3. addOperationWithBlock:(void (^)(void))block:将block中的操作加入队列

这里使用addOperation方式添加任务到队列,并且不设置maxConcurrentOperationCount

- (void)clickNSOperation{
    XXOperation *operationA = [[XXOperation alloc]initWithName:@"operationA"];
    XXOperation *operationB = [[XXOperation alloc]initWithName:@"operationB"];
    XXOperation *operationC = [[XXOperation alloc]initWithName:@"operationC"];
    XXOperation *operationD = [[XXOperation alloc]initWithName:@"operationD"];
    if (!self.operationQueue) {
        self.operationQueue = [[NSOperationQueue alloc]init];
    }
    [self.operationQueue addOperation:operationA];
    [self.operationQueue addOperation:operationB];
    [self.operationQueue addOperation:operationC];
    [self.operationQueue addOperation:operationD];
    
}

输出结果:

  1. 在多个子线程中执行,并发执行;
  2. end在任务之前输出,异步执行。
2019-01-03 17:58:51.841334+0800 Thread[6257:118424] 主线程
2019-01-03 17:58:51.841926+0800 Thread[6257:118424] end---
2019-01-03 17:58:51.841962+0800 Thread[6257:118471] index ==== 0,operName = operationC
2019-01-03 17:58:51.841962+0800 Thread[6257:118472] index ==== 0,operName = operationD
2019-01-03 17:58:51.841963+0800 Thread[6257:118473] index ==== 0,operName = operationB
2019-01-03 17:58:51.841975+0800 Thread[6257:118474] index ==== 0,operName = operationA
2019-01-03 17:58:52.847300+0800 Thread[6257:118471] index ==== 1,operName = operationC
2019-01-03 17:58:52.847303+0800 Thread[6257:118472] index ==== 1,operName = operationD
2019-01-03 17:58:52.847300+0800 Thread[6257:118473] index ==== 1,operName = operationB
2019-01-03 17:58:52.847303+0800 Thread[6257:118474] index ==== 1,operName = operationA
2019-01-03 17:58:53.848148+0800 Thread[6257:118473] index ==== 2,operName = operationB
2019-01-03 17:58:53.848148+0800 Thread[6257:118474] index ==== 2,operName = operationA
2019-01-03 17:58:53.848148+0800 Thread[6257:118472] index ==== 2,operName = operationD
2019-01-03 17:58:53.848173+0800 Thread[6257:118471] index ==== 2,operName = operationC

4.控制串行、并行

NSOperationQueue通过设置maxConcurrentOperationCount来实现任务是串行还是并行。
maxConcurrentOperationCount: 最大操作并发数,用来控制一个队列中有多少个任务同时进行,而不是并发线程的数量。

在NSOperation.h里面,可以看见 maxConcurrentOperationCount默认值为-1

static const NSInteger NSOperationQueueDefaultMaxConcurrentOperationCount = -1;
  1. maxConcurrentOperationCount默认值为-1,不进行限制,可以并行执行;
  2. maxConcurrentOperationCount = 1为串行队列,只能串行执行;
  3. maxConcurrentOperationCount > 1为并行队列,可以并行执行,但是设置的值不能超过系统限制的最大值,如果超过系统限制的最大值,设置的值 = 系统限制的最大值;

此时将maxConcurrentOperationCount 设置为1

- (void)clickNSOperation{
    NSLog(@"主线程");
    XXOperation *operationA = [[XXOperation alloc]initWithName:@"operationA"];
    XXOperation *operationB = [[XXOperation alloc]initWithName:@"operationB"];
    XXOperation *operationC = [[XXOperation alloc]initWithName:@"operationC"];
    XXOperation *operationD = [[XXOperation alloc]initWithName:@"operationD"];
    if (!self.operationQueue) {
        self.operationQueue = [[NSOperationQueue alloc]init];
    }
    self.operationQueue.maxConcurrentOperationCount = 1;//值 = 1,串行队列,
    [self.operationQueue addOperation:operationA];
    [self.operationQueue addOperation:operationB];
    [self.operationQueue addOperation:operationC];
    [self.operationQueue addOperation:operationD];
    NSLog(@"end---");
}

输出结果:

  1. end在任务之前输出,异步执行;
  2. 任务在多个子线程中执行;
  3. 任务一个接着一个串行执行,最后呈现结果就是异步串行。

由此证明:maxConcurrentOperationCount,用来控制一个队列中有多少个任务同时进行,而不是并发线程的数量。

2019-01-03 18:14:37.042902+0800 Thread[6370:123969] 主线程
2019-01-03 18:14:37.043486+0800 Thread[6370:123969] end---
2019-01-03 18:14:37.043535+0800 Thread[6370:124020] index ==== 0,operName = operationA
2019-01-03 18:14:38.043839+0800 Thread[6370:124020] index ==== 1,operName = operationA
2019-01-03 18:14:39.049215+0800 Thread[6370:124020] index ==== 2,operName = operationA
2019-01-03 18:14:40.054252+0800 Thread[6370:124021] index ==== 0,operName = operationB
2019-01-03 18:14:41.059064+0800 Thread[6370:124021] index ==== 1,operName = operationB
2019-01-03 18:14:42.061843+0800 Thread[6370:124021] index ==== 2,operName = operationB
2019-01-03 18:14:43.067389+0800 Thread[6370:124021] index ==== 0,operName = operationC
2019-01-03 18:14:44.072883+0800 Thread[6370:124021] index ==== 1,operName = operationC
2019-01-03 18:14:45.074068+0800 Thread[6370:124021] index ==== 2,operName = operationC
2019-01-03 18:14:46.076217+0800 Thread[6370:124020] index ==== 0,operName = operationD
2019-01-03 18:14:47.079396+0800 Thread[6370:124020] index ==== 1,operName = operationD
2019-01-03 18:14:48.084846+0800 Thread[6370:124020] index ==== 2,operName = operationD

将最大并发数设置为4

self.operationQueue.maxConcurrentOperationCount = 4;//值 = 4,并行队列

输出结果:

  1. end在任务之前输出,异步执行;
  2. 任务在多个子线程中执行;
  3. 任务并发执行,最后呈现结果就是实现异步并发执行(最完美状态)
2019-01-03 18:19:58.120798+0800 Thread[6424:126327] 主线程
2019-01-03 18:19:58.121325+0800 Thread[6424:126327] end---
2019-01-03 18:19:58.121362+0800 Thread[6424:126374] index ==== 0,operName = operationB
2019-01-03 18:19:58.121339+0800 Thread[6424:126371] index ==== 0,operName = operationA
2019-01-03 18:19:58.121362+0800 Thread[6424:126373] index ==== 0,operName = operationD
2019-01-03 18:19:58.121371+0800 Thread[6424:126372] index ==== 0,operName = operationC
2019-01-03 18:19:59.122232+0800 Thread[6424:126372] index ==== 1,operName = operationC
2019-01-03 18:19:59.122231+0800 Thread[6424:126371] index ==== 1,operName = operationA
2019-01-03 18:19:59.122287+0800 Thread[6424:126373] index ==== 1,operName = operationD
2019-01-03 18:19:59.122231+0800 Thread[6424:126374] index ==== 1,operName = operationB
2019-01-03 18:20:00.127789+0800 Thread[6424:126371] index ==== 2,operName = operationA
2019-01-03 18:20:00.127789+0800 Thread[6424:126374] index ==== 2,operName = operationB
2019-01-03 18:20:00.127791+0800 Thread[6424:126372] index ==== 2,operName = operationC
2019-01-03 18:20:00.127791+0800 Thread[6424:126373] index ==== 2,operName = operationD

5. 操作依赖

操作依赖 : 控制任务是否进入就绪状态,从而控制任务执行顺序

  1. - (void)addDependency:(NSOperation *)op;:添加依赖
  2. (void)removeDependency:(NSOperation *)op : 删除依赖
  3. @property (readonly, copy) NSArray<NSOperation *> *dependencies:返回当前依赖数组

上一个例子中,有4个任务,分别是A、B、C、D ,设置D依赖于A ,A 依赖于C,C 依赖于B,千万不能再设置B依赖于D,不然就是死循环了。

- (void)clickNSOperation{
    NSLog(@"主线程");
    XXOperation *operationA = [[XXOperation alloc]initWithName:@"operationA"];
    XXOperation *operationB = [[XXOperation alloc]initWithName:@"operationB"];
    XXOperation *operationC = [[XXOperation alloc]initWithName:@"operationC"];
    XXOperation *operationD = [[XXOperation alloc]initWithName:@"operationD"];
    if (!self.operationQueue) {
        self.operationQueue = [[NSOperationQueue alloc]init];
    }
//    self.operationQueue.maxConcurrentOperationCount = 1;//值 = 1,串行队列
    self.operationQueue.maxConcurrentOperationCount = 4;//值 = 4,并行队列

    // 设置D依赖于A ,A 依赖于C,C 依赖于B
    [operationD addDependency:operationA];
    [operationA addDependency:operationC];
    [operationC addDependency:operationB];
    
    [self.operationQueue addOperation:operationA];
    [self.operationQueue addOperation:operationB];
    [self.operationQueue addOperation:operationC];
    [self.operationQueue addOperation:operationD];
     NSLog(@"end---");
}

输出结果:

  1. end在任务之前输出,异步执行;
  2. 任务执行顺序B、C、A、D。
    本来添加到队列的任务顺序是A、B、C、D,执行A时,A依赖于C,C还未执行结束,A处于未就绪状态,只有等C执行结束后,A才能处于就绪状态,才能执行任务。B没有依赖,直接执行,同理C依赖于B,,D依赖于A,所以执行顺序是B、C、A、D
2019-01-04 16:36:29.830947+0800 Thread[19747:188253] 主线程
2019-01-04 16:36:29.831568+0800 Thread[19747:188253] end---
2019-01-04 16:36:29.831603+0800 Thread[19747:188290] index ==== 0,operName = operationB
2019-01-04 16:36:30.832856+0800 Thread[19747:188290] index ==== 1,operName = operationB
2019-01-04 16:36:31.837187+0800 Thread[19747:188290] index ==== 2,operName = operationB
2019-01-04 16:36:32.842000+0800 Thread[19747:188289] index ==== 0,operName = operationC
2019-01-04 16:36:33.846089+0800 Thread[19747:188289] index ==== 1,operName = operationC
2019-01-04 16:36:34.851604+0800 Thread[19747:188289] index ==== 2,operName = operationC
2019-01-04 16:36:35.856626+0800 Thread[19747:188290] index ==== 0,operName = operationA
2019-01-04 16:36:36.859237+0800 Thread[19747:188290] index ==== 1,operName = operationA
2019-01-04 16:36:37.862654+0800 Thread[19747:188290] index ==== 2,operName = operationA
2019-01-04 16:36:38.866634+0800 Thread[19747:188289] index ==== 0,operName = operationD
2019-01-04 16:36:39.870859+0800 Thread[19747:188289] index ==== 1,operName = operationD
2019-01-04 16:36:40.873716+0800 Thread[19747:188289] index ==== 2,operName = operationD

6.优先级

queuePriority,设置队列中的任务的优先级,目的是:控制进入就绪状态的任务的执行顺序。

没有设置优先级默认是NSOperationQueuePriorityNormal,几个优先级有以下几个选项:

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

什么样的任务处于就绪状态呢?

添加到队列中,并且依赖关系都已经满足,比如上面A依赖于C,C还未执行结束,A就处于未就绪状态,C执行结束后,A才处于就绪状态。

以下几种条件,都是在maxConcurrentOperationCount = 1的情况下,剔除并发引起的问题。

  1. 如果队列中的任务都处于就绪状态下,并且都没有设置优先级,都是默认优先级的时候,任务的执行顺序,就是先添加到队列里面的任务先执行。(控制串行、并行的第一个例子可以证明)

  2. 如果队列中的任务都处于就绪状态下,并且设置优先级的时候,任务的执行顺序,就是优先级越高的任务先执行。

  3. 如果队列中的任务有处于就绪状态的,有未处于就绪状态的,就绪状态的优先级低,未就绪状态的优先级高,此时还是执行就绪状态优先级低的操作,因为依赖关系控制任务是否进入就绪状态,从而控制任务执行顺序,而优先级则是控制进入就绪状态任务的执行顺序。

情况2的实现代码

- (void)clickNSOperation{
    NSLog(@"主线程");
    XXOperation *operationA = [[XXOperation alloc]initWithName:@"operationA"];
    XXOperation *operationB = [[XXOperation alloc]initWithName:@"operationB"];
    XXOperation *operationC = [[XXOperation alloc]initWithName:@"operationC"];
    XXOperation *operationD = [[XXOperation alloc]initWithName:@"operationD"];
    if (!self.operationQueue) {
        self.operationQueue = [[NSOperationQueue alloc]init];
    }
   self.operationQueue.maxConcurrentOperationCount = 1;//值 = 1,串行队列
//    self.operationQueue.maxConcurrentOperationCount = 4;//值 = 4,并行队列
   //设置优先级
    operationA.queuePriority = NSOperationQueuePriorityVeryHigh;
    operationB.queuePriority = NSOperationQueuePriorityVeryHigh;
    operationC.queuePriority = NSOperationQueuePriorityHigh;
    operationD.queuePriority = NSOperationQueuePriorityVeryHigh;
    [self.operationQueue addOperation:operationA];
    [self.operationQueue addOperation:operationB];
    [self.operationQueue addOperation:operationC];
    [self.operationQueue addOperation:operationD];
     NSLog(@"end---");

输出结果:任务执行顺序A、B、D、C,跟任务优先级关系成正比,优先级越高,越先执行,C优先级比D低,所以D先执行。

2019-01-07 10:58:02.635752+0800 Thread[1130:25079] index ==== 0,operName = operationA
2019-01-07 10:58:03.641122+0800 Thread[1130:25079] index ==== 1,operName = operationA
2019-01-07 10:58:04.645798+0800 Thread[1130:25079] index ==== 2,operName = operationA
2019-01-07 10:58:05.651342+0800 Thread[1130:25076] index ==== 0,operName = operationB
2019-01-07 10:58:06.651660+0800 Thread[1130:25076] index ==== 1,operName = operationB
2019-01-07 10:58:07.656731+0800 Thread[1130:25076] index ==== 2,operName = operationB
2019-01-07 10:58:08.661404+0800 Thread[1130:25079] index ==== 0,operName = operationD
2019-01-07 10:58:09.666161+0800 Thread[1130:25079] index ==== 1,operName = operationD
2019-01-07 10:58:10.668855+0800 Thread[1130:25079] index ==== 2,operName = operationD
2019-01-07 10:58:11.672342+0800 Thread[1130:25076] index ==== 0,operName = operationC
2019-01-07 10:58:12.676247+0800 Thread[1130:25076] index ==== 1,operName = operationC
2019-01-07 10:58:13.677245+0800 Thread[1130:25076] index ==== 2,operName = operationC

情况3的实现代码

- (void)clickNSOperation{
    NSLog(@"主线程");
    XXOperation *operationA = [[XXOperation alloc]initWithName:@"operationA"];
    XXOperation *operationB = [[XXOperation alloc]initWithName:@"operationB"];
    XXOperation *operationC = [[XXOperation alloc]initWithName:@"operationC"];
    XXOperation *operationD = [[XXOperation alloc]initWithName:@"operationD"];
    if (!self.operationQueue) {
        self.operationQueue = [[NSOperationQueue alloc]init];
    }
   self.operationQueue.maxConcurrentOperationCount = 1;//值 = 1,串行队列
//    self.operationQueue.maxConcurrentOperationCount = 4;//值 = 4,并行队列
     // 设置D依赖于A ,A 依赖于C,C 依赖于B
    [operationD addDependency:operationA];
    [operationA addDependency:operationC];
    [operationC addDependency:operationB];
     //设置优先级
    operationA.queuePriority = NSOperationQueuePriorityVeryHigh;
    operationB.queuePriority = NSOperationQueuePriorityVeryHigh;
    operationC.queuePriority = NSOperationQueuePriorityHigh;
    operationD.queuePriority = NSOperationQueuePriorityVeryHigh;

    [self.operationQueue addOperation:operationA];
    [self.operationQueue addOperation:operationB];
    [self.operationQueue addOperation:operationC];
    [self.operationQueue addOperation:operationD];
     NSLog(@"end---");

输出结果:
如果只设置依赖关系,不设置优先级,执行顺序是B、C、A、D
如果不设置依赖关系,只设置优先级,执行顺序是A、B、D、C
同时设置依赖关系和优先级,执行顺序依旧是B、C、A、D,因为依赖关系控制任务是否进入就绪状态,从而控制任务执行顺序,而优先级则是控制进入就绪状态任务的执行顺序。因为D依赖于A ,A 依赖于C,C 依赖于B,所以此时就绪状态决定执行顺序。

2019-01-07 11:18:25.281981+0800 Thread[1162:31573] 主线程
2019-01-07 11:18:25.282591+0800 Thread[1162:31573] end---
2019-01-07 11:18:25.282638+0800 Thread[1162:31615] index ==== 0,operName = operationB
2019-01-07 11:18:26.287156+0800 Thread[1162:31615] index ==== 1,operName = operationB
2019-01-07 11:18:27.292264+0800 Thread[1162:31615] index ==== 2,operName = operationB
2019-01-07 11:18:28.297512+0800 Thread[1162:31615] index ==== 0,operName = operationC
2019-01-07 11:18:29.301186+0800 Thread[1162:31615] index ==== 1,operName = operationC
2019-01-07 11:18:30.302530+0800 Thread[1162:31615] index ==== 2,operName = operationC
2019-01-07 11:18:31.304189+0800 Thread[1162:31615] index ==== 0,operName = operationA
2019-01-07 11:18:32.304707+0800 Thread[1162:31615] index ==== 1,operName = operationA
2019-01-07 11:18:33.308931+0800 Thread[1162:31615] index ==== 2,operName = operationA
2019-01-07 11:18:34.309684+0800 Thread[1162:31615] index ==== 0,operName = operationD
2019-01-07 11:18:35.313671+0800 Thread[1162:31615] index ==== 1,operName = operationD
2019-01-07 11:18:36.317910+0800 Thread[1162:31615] index ==== 2,operName = operationD

7.线程间的通信

在iOS开发工程中,我们一般在主线程中进行UI刷新,如:点击、拖拽、滚动事件,耗时操作放在其他线程中,而当耗时操作结束后,回到主线程,就需要用到线程间的通信

- (void)clickNSOperation{
    NSLog(@"主线程");
    self.operationQueue = [[NSOperationQueue alloc]init];
    [self.operationQueue addOperationWithBlock:^{
        for (NSInteger index = 0; index < 3; index ++) {
            NSLog(@"invocation ==== %ld",(long)index);
            [NSThread sleepForTimeInterval:1.0];
        }
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"回到主线程");
            NSLog(@"end---");
        }];
    }];
}

输出结果:完成耗时操作后,回到了主线程

2019-01-07 13:59:09.784407+0800 Thread[1449:46686] 主线程
2019-01-07 13:59:09.785071+0800 Thread[1449:46730] invocation ==== 0
2019-01-07 13:59:10.789103+0800 Thread[1449:46730] invocation ==== 1
2019-01-07 13:59:11.793907+0800 Thread[1449:46730] invocation ==== 2
2019-01-07 13:59:12.796989+0800 Thread[1449:46686] 回到主线程
2019-01-07 13:59:12.797208+0800 Thread[1449:46686] end---

8.线程安全与线程同步

线程安全:在多个线程中同时访问并操作同一对象,运行结果与预期的值相同就是线程安全。线程安全问题都是由全局变量静态变量引起的,若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

线程同步:可理解为线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。

例子还是用之前的卖票例子,两个站点卖票,创建一个TicketManager,在外面调用startToSale方法,TicketManager里面sale方法加锁,保证线程安全。

//  TicketManager.m
//  Thread
//
//  Created by Summer on 2018/12/25.
//  Copyright © 2018 Summer. All rights reserved.
//

#import "TicketManager.h"


#define Total 50

@interface TicketManager ()

@property (nonatomic,assign) NSInteger tickets;//剩余票数
@property (nonatomic,assign) NSInteger saleCount;//卖出票数
//使用NSThread
//@property (nonatomic,strong) NSThread *threadBJ;//北京票点
//@property (nonatomic,strong) NSThread *threadSH;//上海票点
//使用NSInvocationOperation + NSOperationQueue
@property (nonatomic , strong) NSInvocationOperation *operationBJ;
@property (nonatomic , strong) NSInvocationOperation *operationSH;
@property (nonatomic , strong)  NSOperationQueue *queue;
/*
 NSLock、NSConditionLock、NSRecursiveLock、NSCondition加锁方式都一样,都是实现NSLocking协议

 */
@property (nonatomic,strong) NSCondition *condition;
@property (nonatomic , strong) dispatch_semaphore_t semaphore;

@end

@implementation TicketManager

- (instancetype)init{
    self = [super init];
    if (self) {
        _condition = [[NSCondition alloc]init];
        _semaphore = dispatch_semaphore_create(1);
        
        _tickets = Total;
//        _threadBJ = [[NSThread alloc]initWithTarget:self selector:@selector(sale) object:nil];
//        _threadSH = [[NSThread alloc]initWithTarget:self selector:@selector(sale) object:nil];
        
//        [_threadBJ setName:@"北京"];
//        [_threadSH setName:@"上海"];
        
        
        _operationBJ = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(sale) object:nil];
        [_operationBJ setName:@"北京"];
        
        _operationSH = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(sale) object:nil];
        [_operationSH setName:@"上海"];
    }
    return self;
}

- (void)sale{
    while (1) {
        //1、synchronized
        @synchronized (self) {
            if (self.tickets > 0 ) {
                [NSThread sleepForTimeInterval:0.1];
                self.tickets --;
                self.saleCount = Total - self.tickets;
                NSLog(@"%@ , 卖出 = %ld,剩余= %ld",[NSThread currentThread],(long)self.saleCount,(long)self.tickets);
            }else{
                break;//一定要break,不然就会死循环
            }
        }
//        2、NSCondition
//        [self.condition lock];
//        if (self.tickets > 0 ) {
//            [NSThread sleepForTimeInterval:0.1];
//            self.tickets --;
//            self.saleCount = Total - self.tickets;
//            NSLog(@"%@ , 卖出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
//        }else{
//            break;
//        }
//        [self.condition unlock];
//
        //3、dispatch_semaphore方式
//        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
//        if (self.tickets > 0 ) {
//            [NSThread sleepForTimeInterval:0.1];
//            self.tickets --;
//            self.saleCount = Total - self.tickets;
//            NSLog(@"%@ , 卖出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
//        }else{
//            dispatch_semaphore_signal(self.semaphore);
//            break;
//        }
//        dispatch_semaphore_signal(self.semaphore);
    }
}

- (void)startToSale{
//    [_threadSH start];
//    [_threadBJ start];
    
    _queue = [[NSOperationQueue alloc]init];
    [_queue addOperation:_operationBJ];
    [_queue addOperation:_operationSH];
    
}

输出结果:正常卖票

省略一部分
2019-01-07 14:46:23.488836+0800 Thread[1622:63417]  , 卖出 = 45,剩余= 5
2019-01-07 14:46:23.592623+0800 Thread[1622:63417]  , 卖出 = 46,剩余= 4
2019-01-07 14:46:23.695578+0800 Thread[1622:63417]  , 卖出 = 47,剩余= 3
2019-01-07 14:46:23.798669+0800 Thread[1622:63417]  , 卖出 = 48,剩余= 2
2019-01-07 14:46:23.903085+0800 Thread[1622:63417]  , 卖出 = 49,剩余= 1
2019-01-07 14:46:24.007916+0800 Thread[1622:63417]  , 卖出 = 50,剩余= 0

到此NSOperation + NSOperationQueue就结束了,多线程系列也结束了,2019继续加油~~

参考博客:
iOS 多线程:『NSOperation、NSOperationQueue』详尽总结
iOS多线程慕课网视频

文章链接:
iOS 多线程- pThread和NSThread
iOS 多线程-GCD

喜欢就点个赞吧✌️✌️
有错之处,还请指出,感谢🙏🙏

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容