NSOperation 和 NSOperationQueue 是苹果提供的一套多线程解决方案,是基于 GCD 的封装,完全面向对象,代码可读性较高。
NSOperation 是一个抽象类,并不具备封装操作的能力,用作父类用来约束子类,要想封装操作,必须使用它的子类。
我们可以使用系统提供的两个子类 NSBlockOperation 和 NSInvocationOperation,或者我们也可以自定义一个类,继承自 NSOperation,然后在自定义的类的 main 函数中实现具体操作。
默认情况下,单独使用 NSOperation 会在当前线程中同步执行操作,只有配合使用 NSOperationQueue 才能能更好的实现操作的异步执行。
NSInvocationOperation 初始化方法有两个,分别是:
// 该方法中的 id 类型参数 arg 可以传递到 sel 方法中。
- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
- (instancetype)initWithInvocation:(NSInvocation *)inv;
1、- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
NSLog(@"NSInvocationOperation begin");
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationTest:) object:@"test"];
[operation start];
NSLog(@"NSInvocationOperation end");
- (void)operationTest:(id)obj {
NSLog(@"task -- obj: %@, current queue: %@", obj, [NSThread currentThread]);
}
运行后查看打印信息:
2019-09-16 13:58:59.614875+0800 NSOperationSummary[66718:2581783] NSInvocationOperation begin
2019-09-16 13:58:59.616275+0800 NSOperationSummary[66718:2581783] task -- obj: test, current queue: <NSThread: 0x6000025ee900>{number = 1, name = main}
2019-09-16 13:58:59.616440+0800 NSOperationSummary[66718:2581783] NSInvocationOperation end
说明单独使用 NSInvocationOperation 会在 当前线程中 同步 执行操作。
2、- (instancetype)initWithInvocation:(NSInvocation )inv;
NSLog(@"NSInvocationOperation begin");
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:@selector(operationTest:)];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = @selector(operationTest:);
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithInvocation:invocation];
operation start];
NSLog(@"NSInvocationOperation end");
运行后查看打印信息:
2019-09-16 14:04:33.100057+0800 NSOperationSummary[66809:2584211] NSInvocationOperation begin
2019-09-16 14:04:33.100746+0800 NSOperationSummary[66809:2584211] task -- obj: (null), current queue: <NSThread: 0x600002b19480>{number = 1, name = main}
2019-09-16 14:04:33.100934+0800 NSOperationSummary[66809:2584211] NSInvocationOperation end
可以看到运行结果和第一种方法几乎没有区别,两种方法的唯一区别就是第一种方法可以传递数据到操作的方法中
我们看到:NSInvocationOperation 实例对象直接调用 start 方法是在当前线程执行封装的操作,而不是在子线程中执行。也就是说,NSInvocationOperation 实例对象直接调用 start 方法不会开启新线程异步执行,而是同步执行。只有将 NSInvocationOperation 实例对象添加到一个 NSOperationQueue 队列中,才会异步执行操作。
NSBlockOperation 是 NSOperation 的子类。NSBlockOperation 中给我们提供了两个方法:
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;
第一个是类方法,可以通过类方法直接初始化一个 NSBlockOperation 对象。第二个是实例方法,可以给一个已经存在的 NSBlockOperation 对象添加额外的操作。
和 NSInvocationOperation 相比,NSBlockOperation 对象不用添加到操作队列也能开启新线程,但是开启新线程是有条件的。前提是一个 NSBlockOperation 对象需要封装多个操作。
下面例子中,NSBlockOperation 对象只有一个操作,默认会在当前线程执行:
NSLog(@"NSBlockOperation begin");
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task -- %@", [NSThread currentThread]);
}];
[operation start];
NSLog(@"NSBlockOperation end");
运行后查看打印信息:
2019-09-16 14:10:08.479299+0800 NSOperationSummary[66894:2586699] NSBlockOperation begin
2019-09-16 14:10:08.479726+0800 NSOperationSummary[66894:2586699] task -- <NSThread: 0x600003db96c0>{number = 1, name = main}
2019-09-16 14:10:08.479863+0800 NSOperationSummary[66894:2586699] NSBlockOperation end
可以看到,操作是在主线程中同步执行的。
下面我们测试一下封装多个操作后的执行情况:初始化一个 NSBlockOperation 对象,然后调用 addExecutionBlock: 方法给这个 NSBlockOperation 对象添加多个操作。
NSLog(@"NSBlockOperation begin");
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task 1 -- %@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"task 2 -- %@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"task 3 -- %@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"task 4 -- %@", [NSThread currentThread]);
}];
operation start];
NSLog(@"NSBlockOperation end");
运行后查看打印信息:
2019-09-16 14:12:19.959954+0800 NSOperationSummary[66940:2587843] NSBlockOperation begin
2019-09-16 14:12:19.960626+0800 NSOperationSummary[66940:2587936] task 1 -- <NSThread: 0x600002863c80>{number = 3, name = (null)}
2019-09-16 14:12:19.960626+0800 NSOperationSummary[66940:2587934] task 2 -- <NSThread: 0x60000285aa80>{number = 5, name = (null)}
2019-09-16 14:12:19.960645+0800 NSOperationSummary[66940:2587935] task 3 -- <NSThread: 0x60000285d640>{number = 4, name = (null)}
2019-09-16 14:12:19.960692+0800 NSOperationSummary[66940:2587843] task 4 -- <NSThread: 0x60000280e900>{number = 1, name = main}
2019-09-16 14:12:19.960925+0800 NSOperationSummary[66940:2587843] NSBlockOperation end
我们多运行几次会发现四个操作随机会有一个是在当前线程(主线程)执行,而其他都在子线程中执行,至于哪个操作会在当前线程执行是不确定的。
我们可以自定义 Operation 并将操作封装到 main 函数里,或者重写 start 方法。前一种方法非常简单,开发者不需要管理一些状态属性(如 isExecuting、isFinished),当 main 方法返回时,这个 operation 就结束了。这种方式使用起来非常简单,但是灵活性相对于重写 start 方法来说较差。
#import
@interface CustomOperation : NSOperation
@end
#import "CustomOperation.h"
@implementation CustomOperation
- (void)main {
if (self.isCancelled) {
return;
}
NSLog(@"custom operation task begin %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"custom operation task end");
}
@end
NSLog(@"CustomOperation begin");
CustomOperation *operation = [[CustomOperation alloc] init];
[operation start];
NSLog(@"CustomOperation end");
运行后查看打印信息:
2019-09-16 14:16:40.787471+0800 NSOperationSummary[67024:2590226] CustomOperation begin
2019-09-16 14:16:40.787983+0800 NSOperationSummary[67024:2590226] custom operation task begin <NSThread: 0x60000254cc00>{number = 1, name = main}
2019-09-16 14:16:42.789430+0800 NSOperationSummary[67024:2590226] custom operation task end
2019-09-16 14:16:42.789788+0800 NSOperationSummary[67024:2590226] CustomOperation end
可以看到,自定义的 NSOperation 在执行操作时和是 NSInvocationOperation 一样,都是在主线程中同步执行。
如果把自定义 Operation 添加到操作队列中,那么操作会在子线程中异步执行。
NSLog(@"CustomOperation begin");
CustomOperation *operation = [[CustomOperation alloc] init];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
NSLog(@"CustomOperation end");
运行后查看打印信息:
2019-09-16 14:20:25.512055+0800 NSOperationSummary[67088:2592350] CustomOperation begin
2019-09-16 14:20:25.512483+0800 NSOperationSummary[67088:2592350] CustomOperation end
2019-09-16 14:20:25.512595+0800 NSOperationSummary[67088:2592393] custom operation task begin <NSThread: 0x6000004a3ac0>{number = 3, name = (null)}
2019-09-16 14:20:27.516814+0800 NSOperationSummary[67088:2592393] custom operation task end
我们可以看到,通过将自定义 Operation 添加到操作队列中,操作是在在子线程中异步执行的。
注意:把 operation 添加到队列以后,不能再去调用 start 方法,否则在运行时会 crash。报错信息:Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[__NSOperationInternal _start:]: something other than the operation queue it is in is trying to start the receiver’。
所有的 NSOperation 的子类的实例,在执行完所有操作之后都会判断 completionBlock 属性是否存在(被实现),如果存在就会触发这个 block 告诉开发者所有操作已完成,并且 completionBlock 是在子线程中被触发的。
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task 1 -- %@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"task 2 -- %@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"task 3 -- %@", [NSThread currentThread]);
}];
operation.completionBlock = ^{
NSLog(@"all tasks are finished -- %@", [NSThread currentThread]);
};
[operation start];
运行后查看打印信息:
2019-09-16 14:24:29.978385+0800 NSOperationSummary[67156:2594276] task 2 -- <NSThread: 0x6000012ea0c0>{number = 3, name = (null)}
2019-09-16 14:24:29.978384+0800 NSOperationSummary[67156:2594279] task 3 -- <NSThread: 0x6000012b3400>{number = 4, name = (null)}
2019-09-16 14:24:29.978439+0800 NSOperationSummary[67156:2594221] task 1 -- <NSThread: 0x6000012be000>{number = 1, name = main}
2019-09-16 14:24:29.979408+0800 NSOperationSummary[67156:2594279] all tasks are finished -- <NSThread: 0x6000012b3400>{number = 4, name = (null)}
我们可以看到,无论操作有多少,各个操作是在哪个线程执行的,completionBlock 都是在所有操作完成后进行触发,并且是在子线程中。
NSOperationQueue 即为操作的执行队列,即用来存放要执行的操作。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先要使其进入准备就绪状态(就绪状态取决于操作之间的依赖关系),进入就绪状态后的操作的执行顺序由操作之间的相对优先级决定(优先级是操作对象自身的属性)。
队列通过设置最大并发操作数(maxConcurrentOperationCount)可以用来控制操作的并发或串行执行。
NSOperationQueue 共有两种队列:主队列和自定义队列。
凡是添加到主队列中的操作,都会放到主线程中执行(不包括 NSBlockOperation 实例的 addExecutionBlock: 方法添加了额外操作的情况)。
主队列获取方法:
NSOperationQueue *queue = [NSOperationQueue mainQueue];
添加到自定义队列中的操作,会被自动放到子线程中执行。自定义队列包含了串行、并发功能。
自定义队列创建方法:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
将创建好的操作加入到队列中去,共有两种方法:
1、- (void)addOperation:(NSOperation )op;
需要先创建操作,再将创建好的操作加入到创建好的队列中去。
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 创建操作
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationTest:) object:nil];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task2 begin -- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"task2 end");
}];
// 使用 addOperation: 方法将操作添加到队列中
[queue addOperation:operation1];
[queue addOperation:operation2];
运行后查看打印信息:
2019-09-16 14:51:08.837122+0800 NSOperationSummary[67498:2603414] task -- obj: (null), current queue: <NSThread: 0x6000027c7b00>{number = 3, name = (null)}
2019-09-16 14:51:08.837139+0800 NSOperationSummary[67498:2603415] task2 begin -- <NSThread: 0x6000027cfb40>{number = 4, name = (null)}
2019-09-16 14:51:10.837504+0800 NSOperationSummary[67498:2603415] task2 end
可以看出,使用 NSOperation 子类创建操作,并使用 addOperation: 将操作加入到队列后能够开启新线程,并且可以并发执行操作。
2、- (void)addOperationWithBlock:(void (^)(void))block;
无需创建操作,利用 block 添加操作,并直接将包含操作的 block 加入到队列中。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSLog(@"task1 begin -- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"task1 end");
}];
[queue addOperationWithBlock:^{
NSLog(@"task2 begin -- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"task2 end");
}];
[queue addOperationWithBlock:^{
NSLog(@"task3 begin -- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"task3 end");
}];
运行后查看打印信息:
2019-09-16 14:54:57.199471+0800 NSOperationSummary[67565:2605501] task2 begin -- <NSThread: 0x600001522d40>{number = 5, name = (null)}
2019-09-16 14:54:57.199506+0800 NSOperationSummary[67565:2605504] task3 begin -- <NSThread: 0x60000150e2c0>{number = 4, name = (null)}
2019-09-16 14:54:57.199504+0800 NSOperationSummary[67565:2605503] task1 begin -- <NSThread: 0x60000152ee40>{number = 3, name = (null)}
2019-09-16 14:54:59.204222+0800 NSOperationSummary[67565:2605504] task3 end
2019-09-16 14:54:59.204222+0800 NSOperationSummary[67565:2605501] task2 end
2019-09-16 14:54:59.204222+0800 NSOperationSummary[67565:2605503] task1 end
可以看出,使用 addOperationWithBlock: 将操作加入到队列后能够开启新线程,并且可以并发执行操作。
NSOperationQueue 有个关键属性 maxConcurrentOperationCount,叫做最大并发操作数,用来控制一个特定队列中可以有多少个操作同时执行。
这里 maxConcurrentOperationCount 控制的不是并发线程的数量,而是一个队列中能并发执行的最大操作数。
maxConcurrentOperationCount 的取值范围:
当然这个值不应超过系统限制,即使设置了一个很大的值,系统也会自动调整为 默认最大值。
也不能小于 -1,小于 -1 时,运行时会报错,报错信息:erminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘*** -[NSOperationQueue setMaxConcurrentOperationCount:]: count (-2) cannot be negative’
*** First throw call stack: xxxxxx
将最大并发操作数设为 1:
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 最大并发操作数设置为1
queue.maxConcurrentOperationCount = 1;
// 创建操作
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationTest:) object:nil];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task2 begin -- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"task2 end");
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task3 begin -- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"task3 end");
}];
// 使用 addOperation: 方法将操作添加到队列中
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
运行后查看打印信息:
2019-09-16 15:07:42.736183+0800 NSOperationSummary[67790:2612062] task -- obj: (null), current queue: <NSThread: 0x600003ff1c40>{number = 3, name = (null)}
2019-09-16 15:07:42.736934+0800 NSOperationSummary[67790:2612064] task2 begin -- <NSThread: 0x600003fc9fc0>{number = 4, name = (null)}
2019-09-16 15:07:44.739336+0800 NSOperationSummary[67790:2612064] task2 end
2019-09-16 15:07:44.739701+0800 NSOperationSummary[67790:2612062] task3 begin -- <NSThread: 0x600003ff1c40>{number = 3, name = (null)}
2019-09-16 15:07:46.745063+0800 NSOperationSummary[67790:2612062] task3 end
可以看出,当最大并发操作数为 1 时,操作是按顺序串行执行的,一个操作完成之后,下一个操作才开始执行。
NSOperation 和 NSOperationQueue 能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行顺序。
NSOperation 提供了 3 个接口供我们管理和查看依赖关系:
- (void)addDependency:(NSOperation *)op;
添加依赖,使当前操作依赖于操作 op 的完成。
- (void)removeDependency:(NSOperation *)op;
移除依赖,取消当前操作对操作 op 的依赖。
@property (readonly, copy) NSArray
当前操作开始之前需要等待完成的所有操作的数组。
例如,操作 1 依赖于操作 2,我们就可以用下面的代码进行实现:
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task1 begin -- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"task1 end");
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task2 begin -- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"task2 end");
}];
// 添加依赖(op1 依赖于 op2 的完成)
[op1 addDependency:op2];
// 将操作添加到队列中
[queue addOperation:op1];
[queue addOperation:op2];
运行后查看打印信息:
2019-09-16 21:14:12.015462+0800 NSOperationSummary[72423:2731146] task2 begin -- <NSThread: 0x6000004d89c0>{number = 3, name = (null)}
2019-09-16 21:14:14.018101+0800 NSOperationSummary[72423:2731146] task2 end
2019-09-16 21:14:14.018618+0800 NSOperationSummary[72423:2731148] task1 begin -- <NSThread: 0x6000004d4500>{number = 4, name = (null)}
2019-09-16 21:14:16.021389+0800 NSOperationSummary[72423:2731148] task1 end
可以看出,通过添加操作依赖,op1 在 op2 完成之后才开始执行。
NSOperation 提供了 queuePriority(优先级)属性,queuePriority 属性适用于同一队列中的操作,不适用于不同队列中的操作。
默认情况下,所有新建操作的优先级都是 NSOperationQueuePriorityNormal。但是我们可以通过修改 queuePriority 的值来改变当前操作在同一队列中的优先级。
优先级的取值:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
前边我们说过,对于添加到队列中的操作,首先要使其进入准备就绪状态(就绪状态取决于操作之间的依赖关系),进入就绪状态后的操作的执行顺序由操作之间的相对优先级决定(优先级是操作对象自身的属性)。
那么,什么样的操作才是进入就绪状态的操作呢?
当一个操作的所有依赖都已经完成时,该操作对象通常会进入准备就绪状态,等待执行。
我们举个例子:现在有 4 个操作:op1,op2,op3 和 op4,假设 op4 依赖于 op3,op3 依赖于 op2,op2 和 op1 没有相互依赖的关系。
这种情况下, 因为 op1 和 op2 没有依赖关系,所以在 op1、op2 执行之前,它们就是处于准备就绪状态下的操作。因为 op3 和 op4 都有依赖的操作,所以在 op3、op4 执行之前,它们就都不是准备就绪状态下的操作。
这时,queuePriority(优先级) 属性决定了进入准备就绪状态下的操作之间的执行顺序。注意:优先级不能取代依赖关系。
假如 op1 和 op2 是不同优先级的操作,那么队列就会先执行优先级高的操作。
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task1 begin -- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"task1 end");
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task2 begin -- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"task2 end");
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task3 begin -- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"task3 end");
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task4 begin -- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"task4 end");
}];
// 添加依赖
[op4 addDependency:op3]; // op4 依赖于 op3
[op3 addDependency:op2]; // op3 依赖于 op2
// 设置优先级(op2 优先级 高于 op1)
op1.queuePriority = NSOperationQueuePriorityVeryLow;
op2.queuePriority = NSOperationQueuePriorityVeryHigh;
// 将操作添加到队列中
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
运行后查看打印信息:
2019-09-16 21:20:23.821351+0800 NSOperationSummary[72531:2734709] task2 begin -- <NSThread: 0x600003b3c780>{number = 4, name = (null)}
2019-09-16 21:20:23.821374+0800 NSOperationSummary[72531:2734708] task1 begin -- <NSThread: 0x600003b33900>{number = 3, name = (null)}
2019-09-16 21:20:25.823671+0800 NSOperationSummary[72531:2734708] task1 end
2019-09-16 21:20:25.823671+0800 NSOperationSummary[72531:2734709] task2 end
2019-09-16 21:20:25.824014+0800 NSOperationSummary[72531:2734707] task3 begin -- <NSThread: 0x600003b30f40>{number = 5, name = (null)}
2019-09-16 21:20:27.824264+0800 NSOperationSummary[72531:2734707] task3 end
2019-09-16 21:20:27.824561+0800 NSOperationSummary[72531:2734708] task4 begin -- <NSThread: 0x600003b33900>{number = 3, name = (null)}
2019-09-16 21:20:29.826803+0800 NSOperationSummary[72531:2734708] task4 end
多次运行查看打印结果,实际上 task1 begin 的打印偶尔会早于 task2 begin 的打印(只相差几微秒),这种情况可能是系统给不同操作分配的线程的忙碌状态不同导致的。而对于有依赖关系的 op3、op4,op3 一定是在 op2 完成之后执行,op4 一定是在 op3 完成之后执行。
注意:操作的优先级高低不等于操作在队列中排列的顺序。后入队的操作也可能因为优先级高而先被执行。操作在队列中的顺序取决于队列的 addOperation: 方法。
在开发中,我们会在主线程中进行 UI 更新,而把一些耗时操作(如数据下载、封装)放在子线程中执行,当子线程完成了耗时操作,需要回到主线程时,就用到了线程之间的通信功能。
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 添加操作
[queue addOperationWithBlock:^{
NSLog(@"task begin -- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"task end");
// 回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 在主线程进行 UI 更新的操作
NSLog(@"更新 UI -- %@", [NSThread currentThread]);
}];
}];
运行后查看打印信息:
2019-09-16 21:29:46.437980+0800 NSOperationSummary[72665:2738816] task begin -- <NSThread: 0x60000399ad80>{number = 3, name = (null)}
2019-09-16 21:29:48.440922+0800 NSOperationSummary[72665:2738816] task end
2019-09-16 21:29:48.441805+0800 NSOperationSummary[72665:2738770] 更新 UI -- <NSThread: 0x6000039d0c00>{number = 1, name = main}
可以看出,在子线程中成功回到了主线程中。
取消操作:
- (void)cancel;
可取消操作,实质是标记 isCancelled 的状态。
判断操作状态:
- (BOOL)isFinished;
判断操作是否已经结束。
- (BOOL)isCancelled;
判断操作是否已经标记为取消。
- (BOOL)isExecuting;
判断操作是否正在进行。
- (BOOL)isReady;
判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。
操作同步:
- (void)waitUntilFinished;
阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。
- (void)setCompletionBlock:(void (^)(void))block;
completionBlock 会在当前操作执行完毕时执行。
- (void)addDependency:(NSOperation *)op;
添加依赖,使当前操作依赖于操作 op 的完成。
- (void)removeDependency:(NSOperation *)op;
移除依赖,取消当前操作对操作 op 的依赖。
@property (readonly, copy) NSArray
当前操作开始之前需要等待完成的所有操作的数组。
取消/暂停/恢复操作:
- (void)cancelAllOperations;
可以取消队列的所有操作。
@property (getter=isSuspended) BOOL suspended;
isSuspended 判断队列是否处于暂停状态。setSuspended: 可以设置队列的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列。
操作同步:
- (void)waitUntilAllOperationsAreFinished;
阻塞当前线程,直到队列中的操作全部执行完毕。
添加/获取操作:
- (void)addOperation:(NSOperation *)op;
向队列中添加一个 NSOperation 类型的操作对象。
- (void)addOperations:(NSArray
向队列中添加 一组 NSOperation 类型的操作对象,wait 标志是否阻塞当前线程直到所有操作结束。
- (void)addOperationWithBlock:(void (^)(void))block;
向队列中添加一个 NSBlockOperation 类型的操作对象。
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
获取当前队列中包含的的 NSOperation 操作对象(一个操作执行结束后会自动从该数组中清除掉)。
@property (readonly) NSUInteger operationCount;
当前队列中包含的操作的总数。
@property NSInteger maxConcurrentOperationCount;
队列允许的最大并发操作数,可以用来设置一个特定队列中允许同时并发执行的操作的数量,进而也可以达到控制队列中操作的串行或并行执行的目的。
获取队列:
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue;
获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。
@property (class, readonly, strong) NSOperationQueue *mainQueue;
获取主队列。
参考文章:
NSOperation的高级用法
iOS 多线程:『NSOperation、NSOperationQueue』详尽总结
浅析iOS多线程编程之NSOperation
赠人玫瑰,手留余香 O(∩_∩)O 右上角帮忙点个 赞 吧 ???