NSOperationQueue的基本使用

NSOperationQueue队列控制着NSOperation,NSOperationQueue可以串行,并发执行操作。并且有优先级功能。让一个NSOperation操作开始执行,可以直接调用-(void)start方法,也可以添加到NSOperationQueue中,添加之后会自动执行,但是两者不能同时调用。

主队列

1
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

主队列中的操作都会在主线程中执行。

创建队列

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

创建的队列串行与并行是在一起的。不像GCD是分开创建的。

添加操作

一个操作

1
2
3
4
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"currentThread = %@ ", [NSThread currentThread]);
}];
[queue addOperation:operation];

一组操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

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

NSOperation *invocationOperation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:@selector(invocationOperation:)
object:@{@"name": @"lucaslee"}];

NSArray *operations = @[blockOperation, invocationOperation];

[queue addOperations:operations waitUntilFinished:NO];

block形式操作

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

最大并发数

设置最大并发数,需要在添加操作之前进行设置。一般就在创建队列的时候进行设置,不然会设置无效。

  • 小于1,最大值,不受限制。
  • 等于0,不会执行操作
  • 等于1,那么就是一个串行队列,与GCD串行不一样,可能会有多个线程。
  • 大于1,那么就是一个并行队列,并控制最大并发数。

串行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task 1 currentThread = %@ ", [NSThread currentThread]);
}];
[queue addOperation:blockOperation];

blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task 2 currentThread = %@ ", [NSThread currentThread]);
}];

[queue addOperation:blockOperation];

[queue addOperationWithBlock:^{
NSLog(@"task 3 currentThread = %@ ", [NSThread currentThread]);
}];

[queue addOperationWithBlock:^{
NSLog(@"task 4 currentThread = %@ ", [NSThread currentThread]);
}];

[queue addOperationWithBlock:^{
NSLog(@"task 5 currentThread = %@ ", [NSThread currentThread]);
}];

控制台执行结果

1
2
3
4
5
task 1 currentThread = <NSThread: 0x608000276d00>{number = 3, name = (null)}
task 2 currentThread = <NSThread: 0x600000273a40>{number = 4, name = (null)}
task 3 currentThread = <NSThread: 0x608000276d00>{number = 3, name = (null)}
task 4 currentThread = <NSThread: 0x608000276d00>{number = 3, name = (null)}
task 5 currentThread = <NSThread: 0x608000276d00>{number = 3, name = (null)}

严格按照FIFO,顺序执行。虽然是串行执行,但是可以看出会创建有多个线程。这一点与GCD的串行不一样。

并行

1
queue.maxConcurrentOperationCount = 5;

控制台执行结果

1
2
3
4
5
task 3 currentThread = <NSThread: 0x6000002732c0>{number = 3, name = (null)}
task 4 currentThread = <NSThread: 0x608000264bc0>{number = 6, name = (null)}
task 2 currentThread = <NSThread: 0x608000267000>{number = 4, name = (null)}
task 5 currentThread = <NSThread: 0x600000273500>{number = 7, name = (null)}
task 1 currentThread = <NSThread: 0x608000264b40>{number = 5, name = (null)}

串行队列,没有顺序。会创建多个线程。

优先级

设置队列的优先级,与GCD优先级一样。

1
queue.qualityOfService = NSQualityOfServiceUserInteractive;
  • NSQualityOfServiceUserInteractive 主线程
  • NSQualityOfServiceUserInitiated 高
  • NSQualityOfServiceUtility 低
  • NSQualityOfServiceBackground 后台
  • NSQualityOfServiceDefault 默认

挂起

队列是否挂起,默认值为NO。

  • NO 启动队列中的操作,并且执行。
  • YES 正在执行的操作继续执行,未执行的操作不会启动

当设置为YES的时候,还可以继续添加操作,但是会等设置为NO时,操作才会执行。

1
queue.suspended = YES;

取消操作

一旦添加到操作队列中,队列就拥有了这个操作,那么操作就不能被删除,只能够取消。可以使用操作本身的行为取消,也可以使用操作队列中的取消所有的。

取消是异步的,因为正在进行的操作可能需要一些时间才能够完成。

取消所有的操作

队列的行为。只会取消还没有被执行的操作。已经执行的操作是无法取消的。

1
[self.queue cancelAllOperations];

取消单个依赖

操作本身的行为。

1
[operation cancel];

等待操作完成

为了性能,尽可能的异步操作。

特别是在主线程等待操作,这会导致界面卡死,无法响应用户事件。这跟GCD的串行队列一样。只要涉及同步操作都可能导致卡死。

等待操作

阻塞当前线程,会等待operation执行完毕。

1
[operation waitUntilFinished];

等待队列中所有的操作

阻塞当前线程,会等待所有的队列执行完毕。

1
[queue waitUntilAllOperationsAreFinished];

队列名称

1
queue.name = @"com.lucaslz.queue";

依赖

当某个NSOperation依赖于其他的NSOperation时,就可以通过- (void)addDependency:(NSOperation *)op;添加依赖。只有依赖的NSOperation完成时,当前NSOperation才会执行。也可以通过- (void)removeDependency:(NSOperation *)op;删除依赖。

可以看到依赖是操作NSOperation类对象本身的行为,由它自己管理依赖。因此在一个队列或者多个队列中都是可以的。

没有依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task 1 currentThread = %@ ", [NSThread currentThread]);
}];
NSOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task 2 currentThread = %@ ", [NSThread currentThread]);
}];
NSOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task 3 currentThread = %@ ", [NSThread currentThread]);
}];
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];

控制台输出结果

1
2
3
task 2 currentThread = <NSThread: 0x600000078100>{number = 3, name = (null)}
task 1 currentThread = <NSThread: 0x60000026a280>{number = 4, name = (null)}
task 3 currentThread = <NSThread: 0x60800007e180>{number = 5, name = (null)}

可以看到,虽然是按照顺序添加到队列中,但是队列是并发队列,并且没有限制最大并发数,所以不会按顺序执行,即每次运行结果都可能不一致。

有依赖

在添加到队列之前,添加操作的依赖关系。
operation1依赖operation2,operation2依赖operation3。

1
2
[operation1 addDependency:operation2];
[operation2 addDependency:operation3];

控制台输出结果

1
2
3
task 3 currentThread = <NSThread: 0x60000006adc0>{number = 3, name = (null)}
task 2 currentThread = <NSThread: 0x608000262d00>{number = 4, name = (null)}
task 1 currentThread = <NSThread: 0x60000006adc0>{number = 3, name = (null)}

可以看出,执行顺序是Task 3 -> Task 2 -> Task 1。
也就是说A dependency B, B dependency C, 那么执行顺序就是 C -> B -> A。

多个队列

把操作2 加入到队列2中。

1
2
NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
[queue2 addOperation:operation2];

控制台输出结果

1
2
3
task 3 currentThread = <NSThread: 0x608000275380>{number = 3, name = (null)}
task 2 currentThread = <NSThread: 0x600000273c80>{number = 4, name = (null)}
task 1 currentThread = <NSThread: 0x608000275380>{number = 3, name = (null)}

设置了依赖后, 在一个或者多个队列中,执行结果是一样的。也就是一个或者多个队列不影响执行结果。

循环依赖

也就是任务1依赖任务2,任务2依赖任务3,任务3又依赖任务1。

1
2
3
[operation1 addDependency:operation2];
[operation2 addDependency:operation3];
[operation3 addDependency:operation1];

控制台是不会有任何输出的。也就是说操作不会执行。因此要特别注意不能循环依赖。这就好像这三个依赖相互等待对方能够执行。
在iOS开发中,内存管理循环依赖会导致内存泄露,对象无法释放。
UI约束等循环依赖可能会导致约束警告,约束错误,甚至界面上显示的最终结果与预期很不一致。

操作的执行顺序

对于队列中的操作,执行顺序依赖于。 操作的生命周期中 准备状态以及优先级。

  • 操作是否准备好,又需要对象的依赖关系确定
  • 操作的优先级,只能应用于同一个队列中的操作

操作的优先级只是对已经准备好的操作确定执行顺序。 可以理解为先确定依赖关系,再从已经准备好的操作中选择优先级最高的操作执行。

坚持原创技术分享,您的支持将鼓励我继续创作!