iOS 多线程之 NSOperation

668 阅读9分钟

iOS 多线程之 基础概念

iOS 多线程之 NSThread

iOS 多线程之 NSOperation

iOS 多线程之 GCD

NSOperation 简介

NSOperation 作用

配合使用 NSOperation 和 NSOperationQueue 也是苹果提供的一套多线程解决方案。NSOperation 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。

NSOperation 和 GCD 的区别

  1. GCD 是纯 C 语言的 API,而操作队列则是OC的对象。
  2. 在 GCD中,任务用 block 来表示,而 block 是个轻量级的数据结构;操作队列中的操作, 则是个更加重量级的OC对象。

使用NSOperation的好处

  1. 可以方便的调用cancel方法来取消某个操作,而 GCD 中的任务是无法被取消的。
  2. 可以方指定操作间的依赖关系, 方便的控制执行顺序。
  3. 可以通过KVO提供对操作对象的精细控制(如监听当前操作是否被取消或是否已经完成等)。
  4. 可以方便的指定操作优先级。
  5. 可以自定义NSOperation的子类可以实现操作重用.

操作/操作队列

NSOperation 操作:

封装执行的任务。在 GCD 中将任务放在 block 中。

NSOperation 类是一个抽象类,不直接使用,而是使用子类(NSInvocationOperation 或 NSBlockOperation),或者自定义NSOperation,通过实现内部相应的方法来封装操作。执行实际任务。

NSOperationQueue 操作队列:

用来存放和控制操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。操作队列根据操作的优先级和就绪程度执行排队的操作。

实现多线程使用步骤

NSOperation 需要配合 NSOperationQueue 来实现多线程:

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

NSOperation 基本使用

封装(创建)操作

单独使用操作,可以通过调用start方法来自己执行操作。NSOperation是个抽象类,不能用来封装操作。只有使用它的子类来封装操作。有三种方式来封装操作。

  1. 使用子类 NSInvocationOperation
  2. 使用子类 NSBlockOperation
  3. 自定义继承自 NSOperation 的子类,通过实现内部相应的方法来封装操作。

NSInvocationOperation

  • 单独使用NSInvocationOperation在当前线程同步执行,并没有开启新线。
///单独使用 NSInvocationOperation 在当前线程同步执行,并没有开启新线
- (void)invocationOperation {
    //01 封装操作对象
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
    NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
    NSInvocationOperation *op4 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
    //02 执行操作
    [op1 start];
    [op2 start];
    [op3 start];
    [op4 start];
}
- (void)download {
  [NSThread sleepForTimeInterval:2]; //模拟耗时操作
  NSLog(@"%@",NSThread.currentThread);
}

NSBlockOperation

  • 单独使用使NSBlockOperation执行一个操作,操作是在当前线程执行的,并没有开启新线程。
  • 但是,NSBlockOperation还提供了一个方法addExecutionBlock:,通过该方法可以添加额外的任务。
  • 这些任务(包括blockOperationWithBlock:中的任务)可以在不同的线程中同时(并发)执行。
  • 如果添加的任务多,blockOperationWithBlock:中的任务也可能会在其他(非当前)线程中执行,由系统决定。
  • 只有当该操作所有的任务已经完成执行时,才视为完成该操作。
- (void)blockOperation {
    //01 封装操作对象
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];//模拟耗时操作
        NSLog(@"1--%@", NSThread.currentThread);
    }];

    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];//模拟耗时操作
        NSLog(@"2--%@", NSThread.currentThread);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];//模拟耗时操作
        NSLog(@"3.0--%@", NSThread.currentThread);
    }];
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];//模拟耗时操作
        NSLog(@"4--%@", NSThread.currentThread);
    }];
  
    //添加额外的任务
    //当一个操作中的任务数量>1的时候,就会开启子线程和当前线程一起执行任务
    [op3 addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2];//模拟耗时操作
        NSLog(@"3.1--%@", NSThread.currentThread);
    }];
    
    [op3 addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2];//模拟耗时操作
        NSLog(@"3.2--%@", NSThread.currentThread);
    }];
    //02 执行操作
    [op1 start];
    [op2 start];
    [op3 start];
    [op4 start];
}
- (void)blockOperationAddExecutionBlock {
    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"1-%i---%@",i, [NSThread currentThread]);
        }
    }];
    // 2.添加额外的任务
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"2-%i---%@",i, NSThread.currentThread); 
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"3-%i---%@",i, NSThread.currentThread); 
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"4-%i---%@",i,NSThread.currentThread); 
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"5-%i---%@",i, NSThread.currentThread); 
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"6-%i---%@",i, NSThread.currentThread); 
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"7-%i---%@",i, NSThread.currentThread); 
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"8-%i---%@",i, NSThread.currentThread); 
        }
    }];
		 //03 执行操作
    [op start];
}

NSOperation 自定义

如果使用子类不能满足需求,可以自定义NSOperation。然后重写- (void)main方法,在里面实现想执行的任务。当 main 执行完返回的时候,才视为完成该操作。

重写- (void)main方法的注意点:

  • 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)

  • 经常通过- (BOOL)isCancelled方法检测操作是否被取消,对取消做出响应

自定义NSOperation优点:有利于代码的封装和复用

#// ZYOperation.m 文件
#import "ZYOperation.h"

@implementation ZYOperation

- (void)main {
    
    if (self.isCancelled) return;
    for (int i = 0; i < 10000; i++) {
        NSLog(@"%i - %@",i,NSThread.currentThread);
    }
    
    if (self.isCancelled) return;
    for (int i = 10000; i < 20000; i++) {
        NSLog(@"%i - %@",i,NSThread.currentThread);
    }
    
    if (self.isCancelled) return;
    for (int i = 20000; i < 30000; i++) {
        NSLog(@" %i - %@",i,NSThread.currentThread);
    }
    
    if (self.isCancelled) return;
    for (int i = 30000; i < 40000; i++) {
        NSLog(@"%i - %@",i,NSThread.currentThread);
    }
}
@end

单独使用自定义继承自NSOperation的子类的情况下,是在当前线程执行操作,并没有开启新线程。

- (void)customOperation {
    ZYOperation *op = [[ZYOperation alloc] init];
    [op start];
}

NSOperation 常用属性和方法

取消操作方法

- (void)cancel;

可取消操作。此方法不会强制操作代码停止,而是标记 isCancelled 状态为YES。如果操作已经完成执行,则此方法无效。只能取消只能取消队列中处理等待状态的操作,当前前操作不可分割必须执行完毕。

获取操作状态

  • @property(readonly, getter=isCancelled) BOOL cancelled;

指示操作是否已取消状态的布尔值。默认值为 NO。调用此对象的cancel方法可将此属性的值设置为 YES。操作对象负责定期调用此方法,并在返回 YES 时停止操作。

在完成操作任务之前,应该始终检查此属性的值,通常在自定义main方法的开头以及每一个耗时操作之前检查它。一个操作在开始执行之前或在执行过程中的任何时候都有可能被取消。因此,在main方法开头检查属性的值(在整个方法中定期检查)可以在操作被取消时尽快退出。

  • @property(readonly, getter=isExecuting) BOOL executing;

指示操作当前是否正在执行状态的布尔值。如果操作当前正在执行其主任务,则此属性的值为 YES;否则,此属性的值为 NO。

实现并发操作对象时,必须重写此属性的实现,以便返回操作的执行状态。在实现并发操作对象时,必须重写此属性的实现,以便可以返回操作的执行状态。在自定义实现中,每当操作对象的执行状态更改时,都必须为键路径生成KVO通知。

对于非并发操作,不需要重新实现此属性。

  • @property(readonly, getter=isFinished) BOOL finished;

指示操作是否是已完成状态的布尔值。如果操作已完成任务,则此属性的值为YES;如果正在执行该任务或尚未启动该任务,则该属性的值为 NO。

在实现并发操作对象时,必须重写此属性的实现,以便可以返回操作的完成状态。在自定义实现中,每当操作对象的完成状态更改时,都必须为键路径生成KVO通知。有关手动生成KVO通知的更多信息,请参见《键值观察编程指南》isFinished

对于非并发操作,不需要重新实现此属性。

  • @property(readonly, getter=isReady) BOOL ready;

指示操在是否是就绪状态(可以执行)的布尔值。操作的就绪状态取决于它们对其他操作的依赖性,还可能取决于您定义的自定义条件。本NSOperation类管理的其他操作的依赖,并报告基于这些依赖接收的准备。

如果要使用自定义条件来定义操作对象的就绪状态,请重新实现此属性并返回一个准确反映接收方就绪状态的值。如果这样做,您的自定义实现必须从中获取默认属性值super,并将该就绪值合并到该属性的新值中。在自定义实现中,每当操作对象的就绪状态发生更改时,都必须为关键路径生成KVO通知。

依赖关系

- (void)addDependency:(NSOperation *)op;

添加依赖,使当前操作依赖于操作op的完成。如果当前操作已经在执行任务,那么添加依赖项没有实际效果。此方法可能会更改当前操作的isReadydependencies属性。

在一组操作之间创建任何循环依赖关系会导致操作之间出现死锁。

- (void)removeDependency:(NSOperation *)op;

移除依赖,取消当前操作对操作 op 的依赖。此方法可能会更改当前操作的isReadydependencies属性。

@property(readonly, copy) NSArray<NSOperation *> *dependencies;

当前操作依赖的所有操作对象数组。此属性包含一个NSOperation对象数组。要将对象添加到此数组,请使用addDependency:方法。

操作对象必须在其所有依赖的操作完成执行后才能执行。操作完成执行后,不会从此依赖数组中删除操作。可以使用此数组来跟踪所有相关操作,包括已经完成执行的操作。从此列表中删除操作的唯一方法是使用removeDependency:方法。

NSOperationQueue 基本使用

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

主队列

通过mainQueue获得,凡是放到主队列中的任务都将在主线程执行。

  • 注:不包括操作使用addExecutionBlock:添加的额外操作,额外操作可能在其他线程执行。
// 主队列获取方法
NSOperationQueue *queue = [NSOperationQueue mainQueue];

非主队列

直接alloc init获得。非主队列同时具备了并发和串行的功能(默认是并发队列),通过设置最大并发数属性来控制任务是并发执行还是串行执行。

// 自定义队列创建方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

将操作加入到队列中

NSOperation 需要配合 NSOperationQueue 来实现多线程,将创建好的操作加入到队列中。一个操作对象一次最多只能在一个操作队列中,如果该操作已经在另一个队列中,则此方法将抛出NSInvalidArgumentException异常。如果操作当前正在执行或已完成执行,此方法将抛出NSInvalidArgumentException异常。

  • 使用 addOperation: 将操作加入到操作队列后能够开启新线程,进行并发执行。
//01 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

//02 封装操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1-%i---%@",i, NSThread.currentThread);
    }
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2-%i---%@",i, NSThread.currentThread);
    }
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3-%i---%@",i, NSThread.currentThread);
    }
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"4-%i---%@",i, NSThread.currentThread);
    }
}];

//先创建操作,再将操作加入到队列
//03 把操作添加到队列
[queue addOperation:op1]; //内部调用  [op1 start];
[queue addOperation:op2]; //内部调用  [op2 start];
[queue addOperation:op3]; //内部调用  [op3 start];
[queue addOperation:op4]; //内部调用  [op4 start];
  • addOperationWithBlock:

此方法首先将单个 Block 包装在操作对象中,将操作加入到操作队列。不能获取操作对象信息。能够开启新线程,进行并发执行。

- (void)addOperationWithBlock {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  
  //直接将包含操作的 block 加入到队列
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1-%i---%@",i, NSThread.currentThread);
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-%i---%@",i, NSThread.currentThread);
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-%i---%@",i, NSThread.currentThread);
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"4-%i---%@",i, NSThread.currentThread);
        }
    }];
}
  • - (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait

一组操作加入到操作队列, 如果 wait 为 YES 当前线程将被阻塞,添加的一组操作按顺序执行(操作内的任务并发),直到所有指定的操作完成执行。

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

//02 封装操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1-%i---%@",i, NSThread.currentThread);
    }
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2-%i---%@",i, NSThread.currentThread);
    }
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3-%i---%@",i, NSThread.currentThread);
    }
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"4-%i---%@",i, NSThread.currentThread);
    }
}];

//先创建操作,再将操作加入到队列
//03 把操作添加到队列
[queue addOperation:op1]; //内部调用  [op1 start];
//后面的操作必须等待op2, op3(按顺序)执行完成。
[queue addOperation:@[op2, op3] waitUntilFinished:YES]; 
[queue addOperation:op4]; //内部调用  [op4 start];
  • **addBarrierBlock:**阻止在该操作完成之前执行任何后续操作与 dispatch_barrier_async 函数一样。
- (void)addOperationWithBlock {
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1-%i---%@",i, NSThread.currentThread);
        }
    }];
    
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-%i---%@",i, NSThread.currentThread);
        }
    }];
    
    //其作用类似于 dispatch_barrier_async 函数。
    [queue addBarrierBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%i+++++%@",i, NSThread.currentThread);
           }
       }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-%i---%@",i, NSThread.currentThread);
        }
    }];
    
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"4-%i---%@",i, NSThread.currentThread);
        }
    }];
}

控制串行/并发执行

  • NSOperationQueue 创建的自定义队列通过设置 maxConcurrentOperationCount 改变串行、并发功能
  • maxConcurrentOperationCount: 最大并发操作数。用来控制队列中可以有多少个操作同时参与并发执行。
  • 该属性控制的不是并发线程的数量,而是同时能并发执行的最大操作数。
  • 一个操作也并非只能在一个线程中运行.(如果一个操作有多个任务时,这些任可以在不同的线程中并发执行)
  • 该属性默认情况下为-1,表示不进行限制,可进行并发执行。
  • 设置为1时,且每个操作只有一个任务时,队列为串行队列。只能串行执行。一个操作完成之后,下一个操作才开始执行
  • 置为1时,如果有一个操作有多个任务时,该操作内的任务并发执行

操作依赖和监听

NSOperation、NSOperationQueue 它能添加操作之间的依赖关系。通过操作依赖,可以很方便的控制操作之间的执行先后顺序。

  • - (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。

  • - (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。`

  • @property (readonly, copy) NSArray<NSOperation *> *dependencies;

    在当前操作开始执行之前完成执行的所有操作对象数组。

  • @property(copy) void (^completionBlock)(void); 在操作的主任务完成后要执行的块。

//01 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    NSOperationQueue *queue2 = [[NSOperationQueue alloc]init];
    
    //02 封装操作对象
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",NSThread.currentThread);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2----%@",NSThread.currentThread);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",NSThread.currentThread);
    }];
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"4----%@",NSThread.currentThread);
    }];
    
    NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"5----%@",NSThread.currentThread);
    }];
    
    //监听任务执行完毕
    op4.completionBlock = ^{
        NSLog(@"op4完成%@",NSThread.currentThread);
    };
    
    //03 设置操作依赖:4->3->2->1->5
    //⚠️ 不能设置循环依赖,结果就是两个任务都不会执行, 如果有依这两个任务的任务也不会执行
    [op5 addDependency:op1];
    [op1 addDependency:op2];
    //[op2 addDependency:op1];
    [op2 addDependency:op3];
    [op3 addDependency:op4];
    
    //04 把操作添加到队列
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue2 addOperation:op4];
    [queue2 addOperation:op5];

操作优先级

NSOperation 提供了queuePriority(优先级)属性,queuePriority属性适用于同一操作队列中的操作,不适用于不同操作队列中的操作。默认情况下,所有新创建的操作对象优先级都是NSOperationQueuePriorityNormal。但是我们可以通过setQueuePriority:方法来改变当前操作在同一队列中的执行优先级。

// 优先级的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};
  • 当一个操作的所有依赖都已经完成(或没有依赖)时,操作对象通常会进入准备就绪状态,等待执行。
  • 就绪状态下的操作开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)
  • queuePriority 属性,决定了就绪状态下所有操作之间的开始执行顺序。并且,优先级不能取代依赖关系。
  • 一个队列中,既包含高优先级操作,又包含低优先级操作,并且都已经准备就绪,那么队列先执行高优先级操作。
  • 一个队列中,未准备就绪的操作优先级比准备就绪的操作优先级高。也会优先执行准备就绪的操作。
  • 优先级不能取代依赖关系。如果要控制操作间的启动顺序,则必须使用依赖关系。

线程间的通信

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        
        NSURL *url = [NSURL URLWithString:@"http://pic65.nipic.com/file/20150503/19114660_160640830000_2.jpg"];
        NSData *imagweData = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:imagweData];
        
        [NSOperationQueue.mainQueue addOperationWithBlock:^{
            self.imageView.image = image;
        }];
    }];
    //KVO
    //[op addObserver:self forKeyPath:@"isCancelled" options:NSKeyValueObservingOptionNew context:nil];
    [queue addOperation:op];
}

//- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
//}

NSOperationQueue 其他用法

// ZYOperation.m 文件
#import "ZYOperation.h"

@implementation ZYOperation

- (void)main {
    
    if (self.isCancelled) return;
    for (int i = 0; i < 10000; i++) {
        NSLog(@"%i - %@",i,NSThread.currentThread);
    }
    
    if (self.isCancelled) return;
    for (int i = 10000; i < 20000; i++) {
        NSLog(@"%i - %@",i,NSThread.currentThread);
    }
    
    if (self.isCancelled) return;
    for (int i = 20000; i < 30000; i++) {
        NSLog(@" %i - %@",i,NSThread.currentThread);
    }
    
    if (self.isCancelled) return;
    for (int i = 30000; i < 40000; i++) {
        NSLog(@"%i - %@",i,NSThread.currentThread);
    }
}
@end
#import "ViewController.h"
#import "ZYOperation.h"

@interface ViewController ()

@property (nonatomic ,strong) NSOperationQueue *queue;

@end

@implementation ViewController

// 开始
- (IBAction)startClick {
     //[self start1];
  	 [self start2];
}

//暂停
- (IBAction)suspendClick {
    //只能暂停当前操作后面的操作,当前操作不可分割必须执行完毕
    //操作是有状态的
    self.queue.suspended = YES;
}

// 恢复
- (IBAction)resumeClick {
    self.queue.suspended = NO;
}

//取消
- (IBAction)cancelClick {
    //暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。
    //取消 取消所有的操作
    //只能取消队列中处理等待状态的操作,前操作不可分割必须执行完毕
    [self.queue cancelAllOperations];
}

- (void)start1 {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 10000; i++) {
            NSLog(@"1 ~ %i - %@",i,[NSThread currentThread]);
        }
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 10000; i++) {
            NSLog(@"2 ~ %i - %@",i,[NSThread currentThread]);
        }
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 10000; i++) {
           NSLog(@"3 ~ %i - %@",i,[NSThread currentThread]);
        }
    }];
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 10000; i++) {
             NSLog(@"4 ~ %i - %@",i,[NSThread currentThread]);
        }
    }];
    [queue addOperations:@[op1 ,op2, op3, op4] waitUntilFinished:NO];
    
    self.queue = queue;
}

- (void)start2 {
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    
    ZYOperation *op = [[ZYOperation alloc] init];
    
    [queue addOperation:op];
    
    self.queue = queue;
}
@end

iOS 多线程之 基础概念

iOS 多线程之 NSThread

iOS 多线程之 NSOperation

iOS 多线程之 GCD