NSOperation类是iOS2.0推出的,从OS X10.6和iOS4推出GCD时,又重写了NSOperation和NSOperationQueue,NSOperation和NSOperationQueue分别对应GCD的任务和队列,NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。比 GCD 更加灵活,功能也更加强大。
1. Operation Queues(NSOperation,NSOperationQueue) vs. Grand Central Dispatch (GCD)
-
Operation Queues :相对 GCD 来说,使用 Operation Queues 会增加一点点额外的开销,但是我们却换来了非常强大的灵活性和功能,我们可以给 operation 之间添加依赖关系、取消一个正在执行的 operation 、暂停和恢复 operation queue 等;
-
GCD :则是一种更轻量级的,以 FIFO 的顺序执行并发任务的方式,使用 GCD 时我们并不关心任务的调度情况,而让系统帮我们自动处理。但是 GCD 的短板也是非常明显的,比如我们想要给任务之间添加依赖关系、取消或者暂停一个正在执行的任务时就会变得非常棘手;
2. NSOperation,NSOperationQueue
-
NSOperation就是对任务进行的封装。它本身是一个抽象类,不能直接实例化。如果我们想要使用它来执行具体任务,就必须创建自己的子类(继承NSOperation)或者直接使用系统提供的两个子类:NSInvocationOperation 和 NSBlockOperation;
-
NSOperationQueue就是任务的执行队列,可进行串行队列的执行或并发队列的执行;
3. NSOperation开启的两种方式
-
第一种:直接由NSOperation子类对象启动。 首先将需要执行的操作封装到NSOperation子类对象中,然后该对象调用start方法。直接使用start方法,那么操作会在当前线程中同步执行,不会创建新线程。
-
第二种:NSOperation对象添加到NSOperationQueue对象中,由该队列对象启动操作,操作会异步执行:
(1)创建操作:先将需要执行的操作封装到一个 NSOperation 对象中;
(2)创建队列:创建 NSOperationQueue 对象;
(3)将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中;
系统自动从队列中取出线程,并且自动放到线程中执行。
4. NSOperation和NSOperationQueue的基本使用
4.1 NSInvocationOperation简单使用
- 不通过NSOperationQueue
//NSInvocationOperation 同步执行
- (void)invocationOperationSync
{
//创建 operation
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"任务1"];
[operation start];
}
- (void)task:(NSString *)taskName {
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%@---%@", taskName, [NSThread currentThread]);
}
}
打印结果:
2019-07-20 20:18:15.101039+0800 NSOperationDemo[4388:112069] 任务1---<NSThread: 0x6000026d9380>{number = 1, name = main}
2019-07-20 20:18:16.102574+0800 NSOperationDemo[4388:112069] 任务1---<NSThread: 0x6000026d9380>{number = 1, name = main}
在没有使用 NSOperationQueue,在主线程中单独使用使用子类 NSInvocationOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程。
- 通过NSOperationQueue
//NSInvocationOperation 异步执行
- (void)invocationOperationAsync
{
//创建 operation
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"任务1"];
//创建一个操作队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//将任务添加到队列中
[queue addOperation:operation];
}
打印结果:
2019-07-20 20:52:31.394786+0800 NSOperationDemo[4838:122958] 任务1---<NSThread: 0x60000321dcc0>{number = 3, name = (null)}
2019-07-20 20:52:32.396118+0800 NSOperationDemo[4838:122958] 任务1---<NSThread: 0x60000321dcc0>{number = 3, name = (null)}
使用 NSOperationQueue,操作会异步并发执行,会开辟新线程。
4.2 NSBlockOperation简单使用
- 不通过NSOperationQueue
//NSBlockOperation 同步执行
- (void)blockOperationSync
{
//创建 operation
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"block operation---%@", [NSThread currentThread]);
}
}];
[operation start];
}
打印结果:
2019-07-20 21:48:56.673690+0800 NSOperationDemo[5561:142065] block operation---<NSThread: 0x60000018e980>{number = 1, name = main}
2019-07-20 21:48:57.675189+0800 NSOperationDemo[5561:142065] block operation---<NSThread: 0x60000018e980>{number = 1, name = main}
同样在没有使用 NSOperationQueue,不会开启新的线程。
- 通过NSOperationQueue
//NSBlockOperation 异步执行
- (void)blockOperationAsync
{
//创建 operation
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"block operation---%@", [NSThread currentThread]);
}
}];
//创建一个操作队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//将任务添加到队列中
[queue addOperation:operation];
}
打印结果:
2019-07-20 21:54:58.094872+0800 NSOperationDemo[5650:144283] block operation---<NSThread: 0x600002ab1540>{number = 3, name = (null)}
2019-07-20 21:54:59.099257+0800 NSOperationDemo[5650:144283] block operation---<NSThread: 0x600002ab1540>{number = 3, name = (null)}
使用 NSOperationQueue,操作会异步并发执行,会开辟新线程。
另外,NSBlockOperation 还提供了一个方法。
- (void)addExecutionBlock:(void (^)(void))block;
这个方法可以为 NSBlockOperation 添加额外的操作。每个操作可以异步并发执行,所有相关的操作执行完成时,整个操作才算完成。
简单示例:
//NSBlockOperation addExecutionBlock
- (void)blockOperationAddExecutionBlock
{
//创建 operation
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任务1:---%@", [NSThread currentThread]);
}
}];
for (NSInteger i = 2; i < 7; ++i) {
[operation addExecutionBlock:^{
for (NSInteger j = 0; j < 2; ++j) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任务%ld:---%@", i, [NSThread currentThread]);
}
}];
}
[operation start];
}
打印结果:
2019-07-22 10:43:51.309602+0800 NSOperationDemo[2028:55710] 任务4:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:51.309602+0800 NSOperationDemo[2028:55757] 任务1:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
2019-07-22 10:43:51.309602+0800 NSOperationDemo[2028:55755] 任务2:---<NSThread: 0x600000e748c0>{number = 5, name = (null)}
2019-07-22 10:43:51.309611+0800 NSOperationDemo[2028:55758] 任务3:---<NSThread: 0x600000e71400>{number = 4, name = (null)}
2019-07-22 10:43:52.310394+0800 NSOperationDemo[2028:55757] 任务1:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
2019-07-22 10:43:52.310441+0800 NSOperationDemo[2028:55758] 任务3:---<NSThread: 0x600000e71400>{number = 4, name = (null)}
2019-07-22 10:43:52.310392+0800 NSOperationDemo[2028:55710] 任务4:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:52.310452+0800 NSOperationDemo[2028:55755] 任务2:---<NSThread: 0x600000e748c0>{number = 5, name = (null)}
2019-07-22 10:43:53.311787+0800 NSOperationDemo[2028:55757] 任务5:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
2019-07-22 10:43:53.311787+0800 NSOperationDemo[2028:55710] 任务6:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:54.313078+0800 NSOperationDemo[2028:55710] 任务6:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:54.313078+0800 NSOperationDemo[2028:55757] 任务5:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
从打印结果可以看出,任务1的操作没有在主线程执行,任务4的操作在主线程执行,得出:
添加的操作多的话,blockOperationWithBlock: 中的操作也可能会在其他线程(非当前线程)中执行,这是由系统决定的,并不是说添加到 blockOperationWithBlock: 中的操作一定会在当前线程中执行。同样addExecutionBlock: 也是一样,在哪个线程执行是系统决定。
4.3 自定义NSOperation
当系统预定义的两个子类 NSInvocationOperation 和 NSBlockOperation 不能很好的满足我们的需求时,我们可以自定义自己的 NSOperation 子类,添加我们想要的功能。
可以通过重写 main 或者 start 方法 来定义自己的 NSOperation 对象。重写main方法比较简单,我们不需要管理操作的状态属性 isExecuting 和 isFinished。当 main 执行完返回的时候,操作就结束(isFinished为YES)。
简单示例:
//NonConcurrentOperation.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NonConcurrentOperation : NSOperation
@end
NS_ASSUME_NONNULL_END
//NonConcurrentOperation.m
#import "NonConcurrentOperation.h"
@implementation NonConcurrentOperation
- (void)main
{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任务,---%@", [NSThread currentThread]);
}
}
@end
使用NonConcurrentOperation类
- (void)nonConcurrentOperationAction
{
//不加入队列
NonConcurrentOperation *operation = [[NonConcurrentOperation alloc] init];
[operation start];
NSLog(@"---------");
//加入队列
NonConcurrentOperation *operation1 = [[NonConcurrentOperation alloc] init];
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation:operation1];
}
打印结果:
2019-07-22 15:02:22.924678+0800 NSOperationDemo[4590:129265] 任务,---<NSThread: 0x600000eda900>{number = 1, name = main}
2019-07-22 15:02:23.926285+0800 NSOperationDemo[4590:129265] 任务,---<NSThread: 0x600000eda900>{number = 1, name = main}
2019-07-22 15:02:23.926549+0800 NSOperationDemo[4590:129265] ---------
2019-07-22 15:02:24.932274+0800 NSOperationDemo[4590:129326] 任务,---<NSThread: 0x600000eb6380>{number = 3, name = (null)}
2019-07-22 15:02:25.934638+0800 NSOperationDemo[4590:129326] 任务,---<NSThread: 0x600000eb6380>{number = 3, name = (null)}
在默认情况下,operation 是同步执行的,也就是说在调用它的 start 方法的线程中执行它们的任务。而在 operation 和 operation queue 结合使用时,operation queue 可以为非并发的 operation 提供线程,因此,大部分的 operation 仍然可以异步执行。
如果你想要手动地执行一个 operation ,又想这个 operation 能够异步执行的话,你需要做一些额外的配置来让你的 operation 支持并发执行。
下面列举了一些你可能需要重写的方法:
-
start :必须的,所有并发执行的 operation 都必须要重写这个方法,替换掉 NSOperation 类中的默认实现。start 方法是一个 operation 的起点,我们可以在这里配置任务执行的线程或者一些其它的执行环境。另外,需要特别注意的是,在我们重写的 start 方法中一定不要调用父类的实现;
-
main :可选的,通常这个方法就是专门用来实现与该 operation 相关联的任务的。尽管我们可以直接在 start 方法中执行我们的任务,但是用 main 方法来实现我们的任务可以使设置代码和任务代码得到分离,从而使 operation 的结构更清晰;
-
isExecuting 和 isFinished :必须的,并发执行的 operation 需要负责配置它们的执行环境,并且向外界客户报告执行环境的状态。因此,一个并发执行的 operation 必须要维护一些状态信息,用来记录它的任务是否正在执行,是否已经完成执行等。此外,当这两个方法所代表的值发生变化时,我们需要生成相应的 KVO 通知,以便外界能够观察到这些状态的变化;
-
isConcurrent :必须的,这个方法的返回值用来标识一个 operation 是否是并发的 operation ,我们需要重写这个方法并返回 YES 。
注意:当一个 operation 开始执行后,它会一直执行它的任务直到完成或被取消为止。我们可以在任意时间点取消一个 operation ,甚至是在它还未开始执行之前。为了让我们自定义的 operation 能够支持取消事件,我们需要在代码中定期地检查 isCancelled 方法的返回值,一旦检查到这个方法返回 YES ,我们就需要立即停止执行接下来的任务。根据苹果官方的说法,isCancelled 方法本身是足够轻量的,所以就算是频繁地调用它也不会给系统带来太大的负担。
简单示例:
//MyOperation.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface MyOperation : NSOperation
@end
NS_ASSUME_NONNULL_END
//MyOperation.m
#import "MyOperation.h"
@interface MyOperation()
@property (nonatomic, assign) BOOL executing;
@property (nonatomic, assign) BOOL finished;
@end
@implementation MyOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
#pragma mark - start
- (void)start
{
NSLog(@"任务开始");
//开始执行
self.executing = YES;
//执行循环操作
for (NSInteger i = 0; i < 10; ++i) {
if ( self.cancelled ) {
//如果任务已经被取消了,更改状态后,返回
self.executing = NO;
self.finished = YES;
NSLog(@"任务取消,退出");
return;
}
NSLog(@"Task %ld, thread:%@,executing=%d,cancelled=%d,finished=%d,operationCount=%lu", (long)i, [NSThread currentThread], self.executing, self.cancelled, self.finished, (unsigned long)[[NSOperationQueue currentQueue] operationCount]);
[NSThread sleepForTimeInterval:0.2];
}
NSLog(@"任务结束");
self.executing = NO;
self.finished = YES;
NSLog(@"end...thread:%@,executing=%d,cancelled=%d,finished=%d,operationCount=%lu", [NSThread currentThread], self.executing, self.cancelled, self.finished, (unsigned long)[[NSOperationQueue currentQueue] operationCount]);
}
//任务是否是并发
- (BOOL)isAsynchronous
{
return YES;
}
#pragma mark - getter & setter
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:@"executing"];
_executing = executing;
[self didChangeValueForKey:@"executing"];
}
- (BOOL)executing
{
return _executing;
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:@"finished"];
_finished = finished;
[self didChangeValueForKey:@"finished"];
}
@end
使用MyOperation类:
- (void)customOperationAction
{
MyOperation *operation = [[MyOperation alloc] init];
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation:operation];
}
打印结果:
2019-07-22 15:28:09.950993+0800 NSOperationDemo[5038:140737] 任务开始
2019-07-22 15:28:09.951745+0800 NSOperationDemo[5038:140737] Task 0, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.153603+0800 NSOperationDemo[5038:140737] Task 1, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.354902+0800 NSOperationDemo[5038:140737] Task 2, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.557117+0800 NSOperationDemo[5038:140737] Task 3, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.758551+0800 NSOperationDemo[5038:140737] Task 4, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.960494+0800 NSOperationDemo[5038:140737] Task 5, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.163521+0800 NSOperationDemo[5038:140737] Task 6, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.363928+0800 NSOperationDemo[5038:140737] Task 7, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.566943+0800 NSOperationDemo[5038:140737] Task 8, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.767499+0800 NSOperationDemo[5038:140737] Task 9, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.969353+0800 NSOperationDemo[5038:140737] 任务结束
2019-07-22 15:28:11.969781+0800 NSOperationDemo[5038:140737] end...thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=0,cancelled=0,finished=1,operationCount=1
即使一个 operation 是被 cancel 掉了,我们仍然需要手动触发 isFinished 的 KVO 通知。因为当一个 operation 依赖其他 operation 时,它会观察所有其他 operation 的 isFinished 的值的变化,只有当它依赖的所有 operation 的 isFinished 的值为 YES 时,这个 operation 才能够开始执行。因此,如果一个我们自定义的 operation 被取消了但却没有手动触发 isFinished 的 KVO 通知的话,那么所有依赖它的 operation 都不会执行。
4.4 NSOperationQueue 控制串行执行、并发执行
操作队列中有个关键属性 maxConcurrentOperationCount,叫做最大并发操作数。用来控制一个特定队列中可以有多少个操作同时参与并发执行。
maxConcurrentOperationCount的值为:
- -1,默认情况就是-1,表示不进行限制,可进行并发执行;
- 0,不会有任何操作执行;
- 1,队列为串行队列,只能串行执行;
- 大于1,队列为并发队列,这个值也不是无限大,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值};
可以通过
@property (class, readonly, strong) NSOperationQueue *mainQueue API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
获取到主队列。
简单示例:
- (void)testMacConcurrentOperationCount
{
//创建 operation1
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任务1:---%@", [NSThread currentThread]);
}
}];
//创建 operation2
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任务2:---%@", [NSThread currentThread]);
}
}];
//创建 operation3
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任务3:---%@", [NSThread currentThread]);
}
}];
//创建 operation4
NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任务4:---%@", [NSThread currentThread]);
}
}];
NSOperationQueue *queue = [NSOperationQueue new];
// queue.maxConcurrentOperationCount = 0;
// queue.maxConcurrentOperationCount = 1;
// queue.maxConcurrentOperationCount = 2;
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
[queue addOperation:operation4];
}
最大并发操作数为0 打印结果:
没有任何输出。
最大并发操作数为1 打印结果:
2019-07-22 15:44:09.151837+0800 NSOperationDemo[5356:150014] 任务1:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:10.155735+0800 NSOperationDemo[5356:150014] 任务1:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:11.161370+0800 NSOperationDemo[5356:150012] 任务2:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
2019-07-22 15:44:12.167015+0800 NSOperationDemo[5356:150012] 任务2:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
2019-07-22 15:44:13.172285+0800 NSOperationDemo[5356:150014] 任务3:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:14.172991+0800 NSOperationDemo[5356:150014] 任务3:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:15.175897+0800 NSOperationDemo[5356:150012] 任务4:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
2019-07-22 15:44:16.181339+0800 NSOperationDemo[5356:150012] 任务4:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
最大并发操作数为2 打印结果:
2019-07-22 15:45:02.487935+0800 NSOperationDemo[5377:150560] 任务1:---<NSThread: 0x60000040f0c0>{number = 4, name = (null)}
2019-07-22 15:45:02.487965+0800 NSOperationDemo[5377:150558] 任务2:---<NSThread: 0x600000428740>{number = 3, name = (null)}
2019-07-22 15:45:03.488880+0800 NSOperationDemo[5377:150558] 任务2:---<NSThread: 0x600000428740>{number = 3, name = (null)}
2019-07-22 15:45:03.488888+0800 NSOperationDemo[5377:150560] 任务1:---<NSThread: 0x60000040f0c0>{number = 4, name = (null)}
2019-07-22 15:45:04.494115+0800 NSOperationDemo[5377:150561] 任务4:---<NSThread: 0x60000040e940>{number = 5, name = (null)}
2019-07-22 15:45:04.494168+0800 NSOperationDemo[5377:150559] 任务3:---<NSThread: 0x60000040eb80>{number = 6, name = (null)}
2019-07-22 15:45:05.498621+0800 NSOperationDemo[5377:150561] 任务4:---<NSThread: 0x60000040e940>{number = 5, name = (null)}
2019-07-22 15:45:05.498621+0800 NSOperationDemo[5377:150559] 任务3:---<NSThread: 0x60000040eb80>{number = 6, name = (null)}
当最大并发操作数为0时,没有任何操作执行;
当最大并发操作数为1时,操作是按顺序串行执行的;
当最大操作并发数为2时,操作是并发执行的,可以同时执行两个操作;
5. 操作之间的依赖
通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。
配置 operation 的依赖关系主要涉及到 NSOperation 类中的以下两个方法:
//添加依赖,使当前操作依赖于操作 op 的完成
- (void)addDependency:(NSOperation *)op;
//移除依赖,取消当前操作对操作 op 的依赖
- (void)removeDependency:(NSOperation *)op;
简单示例:
- (void)testDependency
{
//创建 operation1
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任务1:---%@", [NSThread currentThread]);
}
}];
//创建 operation2
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任务2:---%@", [NSThread currentThread]);
}
}];
[operation1 addDependency:operation2];
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation:operation1];
[queue addOperation:operation2];
}
打印结果:
2019-07-22 16:29:06.178541+0800 NSOperationDemo[5934:164383] 任务2:---<NSThread: 0x600000d8dbc0>{number = 3, name = (null)}
2019-07-22 16:29:07.184051+0800 NSOperationDemo[5934:164383] 任务2:---<NSThread: 0x600000d8dbc0>{number = 3, name = (null)}
2019-07-22 16:29:08.189099+0800 NSOperationDemo[5934:164386] 任务1:---<NSThread: 0x600000de8380>{number = 4, name = (null)}
2019-07-22 16:29:09.194669+0800 NSOperationDemo[5934:164386] 任务1:---<NSThread: 0x600000de8380>{number = 4, name = (null)}
注意:
- 不要造成循环依赖,不然会出现等待的情况,导致操作都不发执行;
- 另外添加依赖要在队列添加操作之前,因为队列添加操作后,什么时候执行是系统决定的,有可能执行会比添加依赖还更快;
6. NSOperation 队列中的优先级
对于被添加到 操作队列 中的 操作 来说,决定它们执行顺序的第一要素是它们的 isReady 状态,其次是它们在队列中的优先级。操作 的 isReady 状态取决于它的依赖关系,而在队列中的优先级则是 操作本身的属性。默认情况下,所有新创建的操作在队列中的优先级都是 NSOperationQueuePriorityNormal 的,但是我们可以根据需要通过改变queuePriority属性值
@property NSOperationQueuePriority queuePriority;
来提高或降低 operation 的队列优先级。
优先级的取值有:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
需要注意的是,队列优先级只应用于相同操作队列中的操作之间,不同操作队列中的操作不受此影响。
另外,队列优先级和依赖关系之间的关系:
- 操作的队列优先级只决定当前所有 isReady 状态为 YES 的操作的执行顺序。比如,在一个操作队列中,有一个高优先级和一个低优先级的操作 ,并且它们的 isReady 状态都为 YES ,那么高优先级的 operation 将会优先执行。而如果这个高优先级的操作的 isReady 状态为 NO ,而低优先级的操作的 isReady 状态为 YES 的话,那么这个低优先级的操作反而会优先执行;
- 操作的队列中有一个准备就绪状态的操作和一个未准备就绪的操作,未准备就绪的操作优先级比准备就绪的操作优先级高。但是准备就绪的操作依赖于未准备就绪操作的完成。虽然准备就绪的操作优先级低,也会优先执行。优先级不能取代依赖关系。如果要控制操作间的执行顺序,则必须使用依赖关系;
7. 线程之间的通信
场景:耗时操作,比如下载操作会放在子线程中处理,当子线程结束后,需要回到主线程中设置刷新UI。
简单示例:
- (void)threadCommunication
{
//1 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2 创建操作
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
//2.1 执行耗时操作
NSURL *url = [NSURL URLWithString:@"https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1902385933,516700697&fm=26&gp=0.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
NSLog(@"图片下载完毕-----%@",[NSThread currentThread]);
//2.2 获取主队列,回到主线程执行block里面的任务
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
//在主线程中,刷新UI
self.imageView.image = image;
}];
}];
//3 加入队列
[queue addOperation:operation];
}
8. 暂停和恢复操作队列
如果我们想要暂停和恢复执行操作队列中的操作 ,可以通过调用 操作队列的 setSuspended: 方法来实现这个目的。不过需要注意的是,暂停执行操作队列并不能使正在执行的操作暂停执行,而只是简单地暂停调度新的操作。另外,我们并不能单独地暂停执行一个操作,除非直接 cancel 掉。
暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。
参考资料:
iOS 并发编程之 Operation Queues
Demo传送门
over!
网友评论