Contents
  1. 1. NSOperation
    1. 1.1. NSInvocationOperation
      1. 1.1.1. start
      2. 1.1.2. 将操作添加到队列
    2. 1.2. NSBlockOperation (使用较多)
      1. 1.2.1. start
      2. 1.2.2. 将操作添加到队列
      3. 1.2.3. 执行块
      4. 1.2.4. 线程间通讯
      5. 1.2.5. 监听block执行完成
    3. 1.3. 自定义NSOperation
      1. 1.3.1. 自定义类继承NSOperation
      2. 1.3.2. 重写main方法
  2. 2. NSOperationQueue
    1. 2.1. 使用
    2. 2.2. 最大并发数
    3. 2.3. 验证队列的并发性
      1. 2.3.1. NSOperationQueue & NSInvocationOperation
      2. 2.3.2. NSOperationQueue & NSBlockOperation
    4. 2.4. 队列暂停继续和取消全部
      1. 2.4.1. isSuspended:
      2. 2.4.2. cancelAllOperations:
  3. 3. qualityOfService
    1. 3.1. operation
    2. 3.2. queue
  4. 4. 支持KVO的属性
  5. 5. 操作间依赖
    1. 5.1. 需求实例
    2. 5.2. 添加依赖
    3. 5.3. 小结
  6. 6. NSOperation和GCD的区别
    1. 6.1. GCD
      1. 6.1.1. 高级功能
    2. 6.2. NSOperation
      1. 6.2.1. 高级功能

NSOperation

apple提供的多线程解决方案NSOperation是一个表示与单个任务关联的代码和数据的抽象类;因为是一个抽象类,所以不能直接使用,需要使用它的两个子类(NSInvocationOperation or NSBlockOperation) 去执行实际的操作任务;同样我们也可以通过自定义NSOperation。通常将操作添加到操作队列(NSOperationQueue类的实例)来执行操作。其实NSOperation就是对GCD的封装,相对于GCD来说可控性更强,并且可以加入操作依赖(addDependency: and removeDependency)。

NSInvocationOperation

start

- (void)demo1 {
NSInvocationOperation * op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(oprationTask:) object:@"InvocationOperation"];
[op start];
}

- (void)operationTask:(id)param {
NSLog(@"%@",[NSThread currentThread]);
}

输出结果:发现在主线程中输出的结果,但start方法是在当前线程中执行的

2018-05-19 14:45:07.956558+0800 NSOperation练习[1324:290009] <NSThread: 0x604000078000>{number = 1, name = main}

将操作添加到队列

- (void)demo2 {
NSInvocationOperation * op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(oprationTask:) object:@"InvocationOperation"];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperation:op];
}

输出结果:开启子线程异步执行

2018-05-19 14:50:53.013010+0800 NSOperation练习[1408:312296] <NSThread: 0x60400046b600>{number = 3, name = (null)}

NSBlockOperation (使用较多)

start

- (void)demo4 {
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[op start];
}

输出结果:start方法是在当前线程中执行

2018-05-19 15:06:11.837276+0800 NSOperation练习[1661:375754] <NSThread: 0x6000000745c0>{number = 1, name = main}

将操作添加到队列

- (void)demo5 {
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperation:op];
}

输出结果:开启子线程异步执行

2018-05-19 15:10:23.974301+0800 NSOperation练习[1720:389949] <NSThread: 0x60400027ea40>{number = 3, name = (null)}

执行块

- (void)demo6 {
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block 2 %@", [NSThread currentThread]);
}];

[op addExecutionBlock:^{
NSLog(@"block 1 %@", [NSThread currentThread]);
}];


NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperation:op];

NSLog(@"%@", op.executionBlocks);
}

输出结果:执行块和操作享有共同的属性设置,异步执行

2018-05-19 15:20:27.839119+0800 NSOperation练习[1882:431125] (
"<__NSGlobalBlock__: 0x1086f9080>",
"<__NSGlobalBlock__: 0x1086f90c0>"
)
2018-05-19 15:20:27.839131+0800 NSOperation练习[1882:431333] block 2 <NSThread: 0x60000027bbc0>{number = 3, name = (null)}
2018-05-19 15:20:27.839133+0800 NSOperation练习[1882:431330] block 1 <NSThread: 0x604000466fc0>{number = 4, name = (null)}

线程间通讯

- (void)demo7{
[[NSOperationQueue new] addOperationWithBlock:^{
NSLog(@"consuming time:%@",[NSThread currentThread]);
/// 回到主线程刷新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"refresh ui: %@",[NSThread currentThread]);
}];
}];
}

监听block执行完成

- (void)demo13 {
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i=0; i<5; ++i) {
NSLog(@"%zd %@",i,[NSThread currentThread]);
}
}];
//设置监听操作执行完成的block,必须要在把操作添加到队列之前设置
[op setCompletionBlock:^{
NSLog(@"setCompletionBlock %@",[NSThread currentThread]);
}];
[[NSOperationQueue new]addOperation:op];
}

输出结果:

2018-05-19 16:12:00.170053+0800 NSOperation练习[2607:585084] 0 <NSThread: 0x60000027bd80>{number = 3, name = (null)}
2018-05-19 16:12:00.170267+0800 NSOperation练习[2607:585084] 1 <NSThread: 0x60000027bd80>{number = 3, name = (null)}
2018-05-19 16:12:00.170410+0800 NSOperation练习[2607:585084] 2 <NSThread: 0x60000027bd80>{number = 3, name = (null)}
2018-05-19 16:12:00.171319+0800 NSOperation练习[2607:585084] 3 <NSThread: 0x60000027bd80>{number = 3, name = (null)}
2018-05-19 16:12:00.171538+0800 NSOperation练习[2607:585084] 4 <NSThread: 0x60000027bd80>{number = 3, name = (null)}
2018-05-19 16:12:00.171816+0800 NSOperation练习[2607:585083] setCompletionBlock <NSThread: 0x60000027bd40>{number = 4, name = (null)}

自定义NSOperation

自定义类继承NSOperation

@interface CustomOperation : NSOperation

@end
- (void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
DownloadOperation * op = [[DownloadOperation alloc]init];
[queue addOperation:op];
}

重写main方法

任何操作在执行时,首先会调用start方法,start方法会更新操作的状态(过滤操作);经start方法过滤之后,只有正常可执行的操作,就会调用这个main方法,重写操作的入口方法(main方法),就可以在这个方法里面指定操作执行的任务。main方法默认在子线程中异步执行

- (void)main
{
//在这个方法中做想要做的操作,即自定义 NSOperation的目的
NSLog(@"%@",self.URLString,[NSThread currentThread]);
}

NSOperationQueue

使用

NSOperationQueue只有一种类型,就是并发队列。在开发使用到NSOperationQueue时,建议将其定义为全局队列。

// 定义为属性
@property (nonatomic,strong) NSOperationQueue *queue;

- (NSOperationQueue * )queue
{
if (self.queue == nil) {
self.queue = [[NSOperationQueue alloc] init];
}
return self.queue;
}

最大并发数


maxConcurrentOperationCount是队列的一个属性,可以限制队列同时执行的任务数量,从而间接的控制了线程数量(线程可以复用),但队列最大并发数不是线程数。如果队列最大并发数设置为1,那么队列实际上就是一个串行队列了。

// 设置最大并发数 : 每次只能调度两个操作执行
queue.maxConcurrentOperationCount = 2;

验证队列的并发性

NSOperationQueue & NSInvocationOperation

- (void)demo8 {
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
for (NSInteger i=0; i<10; ++i) {
NSInvocationOperation * op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operationTask:) object:@(i)];
[queue addOperation:op];
}
}

输出结果:会开启多条线程,不是顺序执行.与GCD中并发队列&异步执行效果一样,说明NSOperationQueue默认是并发执行

2018-05-19 15:01:47.478515+0800 NSOperation练习[1593:358939] <NSThread: 0x60400047a580>{number = 3, name = (null)}
2018-05-19 15:01:47.478518+0800 NSOperation练习[1593:358938] <NSThread: 0x600000263680>{number = 4, name = (null)}
2018-05-19 15:01:47.478519+0800 NSOperation练习[1593:358936] <NSThread: 0x60400047a500>{number = 6, name = (null)}
2018-05-19 15:01:47.478572+0800 NSOperation练习[1593:358935] <NSThread: 0x60400047a640>{number = 5, name = (null)}
2018-05-19 15:01:47.478740+0800 NSOperation练习[1593:358974] <NSThread: 0x600000263a40>{number = 7, name = (null)}
2018-05-19 15:01:47.478881+0800 NSOperation练习[1593:358938] <NSThread: 0x600000263680>{number = 4, name = (null)}
2018-05-19 15:01:47.478902+0800 NSOperation练习[1593:358939] <NSThread: 0x60400047a580>{number = 3, name = (null)}
2018-05-19 15:01:47.479084+0800 NSOperation练习[1593:358975] <NSThread: 0x6000002636c0>{number = 8, name = (null)}
2018-05-19 15:01:47.479096+0800 NSOperation练习[1593:358976] <NSThread: 0x600000263d00>{number = 10, name = (null)}
2018-05-19 15:01:47.479128+0800 NSOperation练习[1593:358977] <NSThread: 0x600000263dc0>{number = 9, name = (null)}

NSOperationQueue & NSBlockOperation

- (void)demo10 {
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
for (NSInteger i=0; i<10; ++i) {
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[queue addOperation:op];
}
}

输出结果:会开启多条线程,不是顺序执行.与GCD中并发队列&异步执行效果一样,说明NSOperationQueue默认是并发执行

2018-05-19 15:26:52.141566+0800 NSOperation练习[1984:456384] <NSThread: 0x60000027ef80>{number = 5, name = (null)}
2018-05-19 15:26:52.141566+0800 NSOperation练习[1984:456319] <NSThread: 0x604000468dc0>{number = 4, name = (null)}
2018-05-19 15:26:52.141569+0800 NSOperation练习[1984:456314] <NSThread: 0x60000027f040>{number = 3, name = (null)}
2018-05-19 15:26:52.141641+0800 NSOperation练习[1984:456313] <NSThread: 0x604000469d80>{number = 6, name = (null)}
2018-05-19 15:26:52.141661+0800 NSOperation练习[1984:456385] <NSThread: 0x60000027ee80>{number = 8, name = (null)}
2018-05-19 15:26:52.141676+0800 NSOperation练习[1984:456318] <NSThread: 0x604000469c40>{number = 7, name = (null)}
2018-05-19 15:26:52.141928+0800 NSOperation练习[1984:456384] <NSThread: 0x60000027ef80>{number = 5, name = (null)}
2018-05-19 15:26:52.141945+0800 NSOperation练习[1984:456319] <NSThread: 0x604000468dc0>{number = 4, name = (null)}
2018-05-19 15:26:52.141954+0800 NSOperation练习[1984:456314] <NSThread: 0x60000027f040>{number = 3, name = (null)}
2018-05-19 15:26:52.142048+0800 NSOperation练习[1984:456386] <NSThread: 0x60000027f380>{number = 9, name = (null)}

队列暂停继续和取消全部

isSuspended:

暂停和继续队列的属性;YES代表暂停队列,NO代表恢复队列。将队列挂起之后,队列中的操作就不会被调度,但是正在执行的操作不受影响
operationCount: 操作计数,没有执行和没有执行完的操作,都会计算在操作计数之内

注意 : 如果先暂停队列,再添加操作到队列,队列不会调度操作执行.所以在暂停队列之前要判断队列中有没有任务.如果没有操作就不暂停队列.

#pragma mark - 演示队列的暂停
- (IBAction)pause:(id)sender
{
// 暂停队列之前判断队列中有无操作
if (self.queue.operationCount == 0) {
return;
}

// 暂停队列
self.queue.suspended = YES;
NSLog(@"pause %zd",self.queue.operationCount);
}

cancelAllOperations:

取消队列中的全部操作;旦调用的 cancelAllOperations方法,队列中的操作,都会被移除,正在执行的操作除外;正在执行的操作取消不了,如果要取消,需要自定义NSOperation;队列取消全部操作时,会有一定的时间延迟。

- (IBAction)cancelAll:(id)sender
{
[self.queue cancelAllOperations];
NSLog(@"cancelAll %zd",self.queue.operationCount);
}

qualityOfService

服务质量的枚举:

typedef NS_ENUM(NSInteger, NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21,
NSQualityOfServiceUserInitiated = 0x19,
NSQualityOfServiceUtility = 0x11,
NSQualityOfServiceBackground = 0x09,
NSQualityOfServiceDefault = -1
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));

operation

让队列里面的操作有更多的机会被队列调度执行,类似于线程优先级

- (void)demo11 {
NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op1 block %@",[NSThread currentThread]);
}];
for (NSInteger i=0; i<5; ++i) {
[op1 addExecutionBlock:^{
NSLog(@"opt1 %@",[NSThread currentThread]);
}];
}
op1.qualityOfService = NSQualityOfServiceBackground;

NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op2 block %@",[NSThread currentThread]);
}];
for (NSInteger i=0; i<5; ++i) {
[op2 addExecutionBlock:^{
NSLog(@"opt2 %@",[NSThread currentThread]);
}];
}

op2.qualityOfService = NSQualityOfServiceUserInteractive;

NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperations:@[op1,op2] waitUntilFinished:false];
}

输出结果:op2优先于op1执行

2018-05-19 16:05:46.292652+0800 NSOperation练习[2511:564443] op2 block <NSThread: 0x600000278580>{number = 4, name = (null)}
2018-05-19 16:05:46.292656+0800 NSOperation练习[2511:564453] opt2 <NSThread: 0x600000278680>{number = 7, name = (null)}
2018-05-19 16:05:46.292654+0800 NSOperation练习[2511:564445] opt2 <NSThread: 0x604000470ac0>{number = 6, name = (null)}
2018-05-19 16:05:46.292709+0800 NSOperation练习[2511:564454] opt2 <NSThread: 0x604000470c40>{number = 8, name = (null)}
2018-05-19 16:05:46.292933+0800 NSOperation练习[2511:564453] opt2 <NSThread: 0x600000278680>{number = 7, name = (null)}
2018-05-19 16:05:46.292937+0800 NSOperation练习[2511:564443] opt2 <NSThread: 0x600000278580>{number = 4, name = (null)}
2018-05-19 16:05:46.292944+0800 NSOperation练习[2511:564446] opt1 <NSThread: 0x604000470b00>{number = 5, name = (null)}
2018-05-19 16:05:46.292960+0800 NSOperation练习[2511:564444] op1 block <NSThread: 0x604000470b80>{number = 3, name = (null)}
2018-05-19 16:05:46.293083+0800 NSOperation练习[2511:564454] opt1 <NSThread: 0x604000470c40>{number = 8, name = (null)}
2018-05-19 16:05:46.293089+0800 NSOperation练习[2511:564453] opt1 <NSThread: 0x600000278680>{number = 7, name = (null)}
2018-05-19 16:05:46.294076+0800 NSOperation练习[2511:564446] opt1 <NSThread: 0x604000470b00>{number = 5, name = (null)}
2018-05-19 16:05:46.294207+0800 NSOperation练习[2511:564444] opt1 <NSThread: 0x604000470b80>{number = 3, name = (null)}

queue

- (void)demo12 {
NSOperationQueue * q1 = [[NSOperationQueue alloc]init];
NSOperationQueue * q2 = [[NSOperationQueue alloc]init];

q1.qualityOfService = NSQualityOfServiceBackground;
q2.qualityOfService = NSQualityOfServiceUserInteractive;

for (NSInteger i=0; i<5; ++i) {
[q1 addOperationWithBlock:^{
NSLog(@"q1");
}];
[q2 addOperationWithBlock:^{
NSLog(@"q2");
}];
}
}

输出结果:q2优先于q1执行

2018-05-19 16:05:11.965991+0800 NSOperation练习[2487:561497] q2 <NSThread: 0x604000274e00>{number = 4, name = (null)}
2018-05-19 16:05:11.965998+0800 NSOperation练习[2487:561496] q2 <NSThread: 0x600000275740>{number = 3, name = (null)}
2018-05-19 16:05:11.966106+0800 NSOperation练习[2487:561516] q2 <NSThread: 0x600000275940>{number = 5, name = (null)}
2018-05-19 16:05:11.966106+0800 NSOperation练习[2487:561517] q2 <NSThread: 0x604000274f80>{number = 6, name = (null)}
2018-05-19 16:05:11.966316+0800 NSOperation练习[2487:561516] q2 <NSThread: 0x600000275940>{number = 5, name = (null)}
2018-05-19 16:05:11.968284+0800 NSOperation练习[2487:561494] q1 <NSThread: 0x600000263840>{number = 7, name = (null)}
2018-05-19 16:05:11.968303+0800 NSOperation练习[2487:561495] q1 <NSThread: 0x604000275680>{number = 8, name = (null)}
2018-05-19 16:05:11.968376+0800 NSOperation练习[2487:561517] q1 <NSThread: 0x604000274f80>{number = 6, name = (null)}
2018-05-19 16:05:11.968409+0800 NSOperation练习[2487:561496] q1 <NSThread: 0x600000275740>{number = 3, name = (null)}
2018-05-19 16:05:11.969839+0800 NSOperation练习[2487:561494] q1 <NSThread: 0x600000263840>{number = 7, name = (null)}

支持KVO的属性

isCancelled - read-only   //是否取消

isAsynchronous - read-only //是否异步

isExecuting - read-only //是否正在执行

isFinished - read-only //是否结束

isReady - read-only //是否就绪

dependencies - read-only //依赖的其他的操作

queuePriority - readable and writable //队列优先级

completionBlock - readable and writable //结束回调

操作间依赖

需求实例

场景:用户需要先登录->付费->下载->通知用户

- (void)dependency
{
NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"login %@",[NSThread currentThread]);
}];

NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"pay %@",[NSThread currentThread]);
}];

NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download %@",[NSThread currentThread]);
}];

NSBlockOperation * op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"notice user %@",[NSThread currentThread]);
}];
}

添加依赖

[op2 addDependency:op1]; // 操作2依赖于操作1
[op3 addDependency:op2]; // 操作3依赖于操作2
[op4 addDependency:op3]; // 操作4依赖于操作3

// waitUntilFinished : 是否等到指定的操作执行结束再执行后面的代码
[self.queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];

// 通知用户的操作在主线程中执行
[[NSOperationQueue mainQueue] addOperation:op4];

// 验证 waitUntilFinished
NSLog(@"end");

小结

  • 不能循环建立操作间依赖关系.否则,队列不调度操作执行
  • 操作间可以跨队列建立依赖关系
  • 要将操作间的依赖建立好了之后,再添加到队列中(先建立操作依赖关系,再把操作添加到队列)

NSOperation和GCD的区别

GCD

GCD iOS 4.0 推出,针对多核处理器的并发技术。GCD属于C语言的框架。将任务封装在block中,如果要停止已经加入 队列(queue) 的 任务(block) 需要写复杂的代码。只能设置队列的优先级不能设置任务的优先级。

高级功能

  • barrier
  • once
  • after
  • group

NSOperation

NSOperation iOS 2.0 推出,但在苹果推出 GCD 之后,对NSOperation的底层全部重写。NSOperation属于OC 框架,更加面向对象,底层是对 GCD 的封装。支持取消掉队列中的任务(正在执行的除外),还可以设置队列中每个操作的优先级

高级功能

  • 最大操作并发数(GCD不好做)
  • 继续/暂停/全部取消
  • 跨队列设置操作的依赖关系
Contents
  1. 1. NSOperation
    1. 1.1. NSInvocationOperation
      1. 1.1.1. start
      2. 1.1.2. 将操作添加到队列
    2. 1.2. NSBlockOperation (使用较多)
      1. 1.2.1. start
      2. 1.2.2. 将操作添加到队列
      3. 1.2.3. 执行块
      4. 1.2.4. 线程间通讯
      5. 1.2.5. 监听block执行完成
    3. 1.3. 自定义NSOperation
      1. 1.3.1. 自定义类继承NSOperation
      2. 1.3.2. 重写main方法
  2. 2. NSOperationQueue
    1. 2.1. 使用
    2. 2.2. 最大并发数
    3. 2.3. 验证队列的并发性
      1. 2.3.1. NSOperationQueue & NSInvocationOperation
      2. 2.3.2. NSOperationQueue & NSBlockOperation
    4. 2.4. 队列暂停继续和取消全部
      1. 2.4.1. isSuspended:
      2. 2.4.2. cancelAllOperations:
  3. 3. qualityOfService
    1. 3.1. operation
    2. 3.2. queue
  4. 4. 支持KVO的属性
  5. 5. 操作间依赖
    1. 5.1. 需求实例
    2. 5.2. 添加依赖
    3. 5.3. 小结
  6. 6. NSOperation和GCD的区别
    1. 6.1. GCD
      1. 6.1.1. 高级功能
    2. 6.2. NSOperation
      1. 6.2.1. 高级功能