NSOperation、NSOperationQueue是苹果提供给我们的一套多线程解决方案。
实际上NSOperation、NSOperationQueue是基于GCD更高一层的封装,完全面向对象。
比GCD更简单易用、代码可读性也更高。
NSOperation是基于GCD的更高一层的封装,GCD中的一些概念同样适用于NSOperation、NSOperationQueue。
在NSOperation、NSOperationQueue中也有类似任务(操作)、队列(操作队列)的概念:
cancel
isFinished
判断操作是否已经结束isCancelled
判断操作是否取消isExecuting
判断操作是否正在运行isReady
判断是否处于就绪状态isAsynchronous
表示任务是并发还是同步执行waitUntilFinished
阻塞当前线程setCompletionBlock:(void (^)(void))block;
当前操作完毕之后执行blockaddDependency
添加依赖removeDependency
移除依赖@property (readonly, copy) NSArray *dependencies;
数组存储操作cancelAllOperations;
取消队列中所有的操作isSuspended
判断队列是否处理暂停状态 YES:暂停状态,NO恢复状态setSuspended:(BOOL)b;
设置操作的暂停和恢复 YES:暂停,NO:恢复waitUntilAllOperationsAreFinished;
阻塞当前线程,直到队列中的操作全部完成addOperationWithBlock:(void (^)(void))block
向队列中添加一个NSBlockOperation类型的操作对象addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;
添加操作数组,wait标志是否阻塞当前线程知道所有操作结束operations
当前在队列中的操作数组operationCount
操作个数currentQueue
当前队列,如果当前线程不在Queue上,返回nilmainQueue
获取主队列注意:
暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。
NSOperation实现多线程的使用步骤分为三步:
之后,系统会自动将NSOperationQueue中的NSOperation取出来,在新线程中执行操作。
因为NSOperation是个抽象类,不能创建实例,所以我们通常使用它的子类来进行封装操作:
// 1.创建 NSInvocationOperation 对象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testOp) object:nil];
// 2.调用 start 方法开始执行操作
[op start];
- (void)testOp {
NSLog(@"testOp--%@", [NSThread currentThread]);
}
输出结果:
// 1.创建 NSBlockOperation 对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op---%@", [NSThread currentThread]);
}];
// 2.调用 start 方法开始执行操作
[op start];
输出结果:
NSBlockOperation还有一个方法addExecutionBlock:
,通过addExecutionBlock:
就可以为NSBlockOperation添加额外的操作。这些操作(包括blockOperationWithBlock中的操作)可以在不同的线程中同时(并发)执行。只有当所有相关的操作已经完成执行时,才视为完成。
如果添加的操作多的话,blockOperationWithBlock:中的操作也有可能会在其他线程(非当前线程)中执行,这是由系统决定的,并不是说添加到blockOperationWithBlock:中的操作一定在当前线程中执行。
// 1.创建 NSBlockOperation 对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op---%@", [NSThread currentThread]);
}];
// 2.添加额外操作
[op addExecutionBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"1--Block task, %@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"2--Block task, %@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"3--Block task, %@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"4--Block task, %@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"5--Block task, %@", [NSThread currentThread]);
}];
// 3.调用 start 方法开始执行操作
[op start];
输出结果:
blockOperationWithBlock:
方法中的操作有可能不会在当前线程中执行。注意:addExecutionBlock: 方法必须在start()方法之前执行,否则就会报错。
我们可以通过自定义继承自NSOperation的子类,重写main或者start来定义自己的NSOperation对象。
main
方法,有底层控制变更任务执行、完成状态以及任务退出。start
方法,需要自己控制任务状态。重写main方法比较简单,我们不需要管理线程的状态属性executing(是否正在执行)和finished(是否完成)。当main执行完返回的时候,这个操作就结束了。
// .h 文件
#import <Foundation/Foundation.h>
@interface NSOperationTest : NSOperation
@end
// .m 文件
#import "NSOperationTest.h"
@implementation NSOperationTest
- (void)main {
if (!self.isCancelled) {
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"test---%@", [NSThread currentThread]);
}
}
}
@end
// 定义并执行
NSOperationTest *test = [[NSOperationTest alloc] init];
NSLog(@"%d", test.finished);
[test start];
NSLog(@"%d", test.finished);
输出结果:
// .h 文件
#import <Foundation/Foundation.h>
@interface NSOperationTest : NSOperation
@end
// .m 文件
#import "NSOperationTest.h"
@implementation NSOperationTest
- (void)start {
if (!self.isCancelled) {
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"test---%@", [NSThread currentThread]);
}
}
}
@end
// 定义并执行
NSOperationTest *test = [[NSOperationTest alloc] init];
NSLog(@"%d", test.finished);
[test start];
NSLog(@"%d", test.finished);
输出结果:
NSOperationQueue共有两种队列:主队列、自定义队列,其中自定义队列同时包含了串行、并发功能。
// 主队列获取方法
NSOperationQueue *queue = [NSOperationQueue mainQueue];
// 自定义队列(非主队列)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation需要配合NSOperationQueue才能实现多线程,将创建好的操作加入到队列中,有两种方法:
- (void)addOperation:(NSOperation *)op;
方法创建好队列、操作,再将操作都加到队列中:
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建操作
NSInvocationOperation *firstOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationFirst) object:nil];
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2---%@", [NSThread currentThread]);
}];
[secondOperation addExecutionBlock:^{
NSLog(@"add--%@", [NSThread currentThread]);
}];
NSInvocationOperation *thirdOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationThird) object:nil];
// 3.将操作加到队列中
[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];
- (void)operationFirst {
NSLog(@"1---%@", [NSThread currentThread]);
}
- (void)operationThird {
NSLog(@"3---%@", [NSThread currentThread]);
}
输出结果:
- (void)addOperationWithBlock:(void (^)(void)block);
方法不用创建操作,直接在block中添加操作,将block加入到队列中:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"1---%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"2---%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"3---%@", [NSThread currentThread]);
}];
输出结果:
但是,我们看上面的例子,基本上都是并行执行的,下面就来说说怎么控制并发和串行:
操作队列有一个属性,最大并发操作数,用来控制一个特定的队列中可以有多少个操作同时并发执行,也就是一个队列中同时能并发执行的最大操作数:
@property NSInteger maxConcurrentOperationCount;
maxConcurrentOperationCount
默认为-1,表示不进行限制,可进行并发执行maxConcurrentOperationCount
为1时,此时队列为串行队列,只能串行执行maxConcurrentOperationCount
大于1时,队列为并发队列。操作并发执行。当这个值超过了系统限制,就会自动调整为系统设定的值。NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//queue.maxConcurrentOperationCount = 1;
queue.maxConcurrentOperationCount = 2;
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"1---%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"2---%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"3---%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"4---%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"5---%@", [NSThread currentThread]);
}];
输出结果:
最大并发数为1:
最大并发数为2:
可能从输出结果上看不出什么,主要是输出的过程,你可以自己用上述代码试试,当为 1 的时候,明显能看出来每个操作之间都有2秒的延时,但是为 2 的时候,它延迟两秒之后几乎同时输出的。
对于开启线程数,是由系统决定的,不需要我们来管理。
NSOperation、NSOperationQueue最吸引人的就是它能够添加操作之间的依赖关系,通过依赖关系,我们就可以很方便的控制操作之间的执行先后顺序。
NSOperation提供了3个接口供我们使用依赖
- (void)addDependency:(NSOperation *)op
添加依赖,是当前操作依赖op的完成,op完成之后才会执行当前操作。- (void)removeDependency:(NSOperation *)op
移除依赖,取消当前操作对操作op的依赖。@property (readonly, copy) NSArray *dependencies;
操作对象的一个属性,在当前操作开始执行之前完成执行的所有操作对象数组。NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"firstOperation");
}];
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"secondOperation");
}];
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"thirdOperation");
}];
[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];
输出结果:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"firstOperation");
}];
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"secondOperation");
}];
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"thirdOperation");
}];
[secondOperation addDependency:firstOperation]; // 让secondOperation依赖于firstOperation,即firstOperation先执行,在执行secondOperation
[thirdOperation addDependency:secondOperation]; // 让thirdOperation依赖于secondOperation,即secondOperation先执行,在执行thirdOperation
[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];
输出结果:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"firstOperation");
}];
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"secondOperation");
}];
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"thirdOperation");
}];
[secondOperation addDependency:firstOperation]; // 让secondOperation依赖于firstOperation,即firstOperation先执行,在执行secondOperation
[firstOperation addDependency:secondOperation]; // 让firstOperation依赖于secondOperation,即secondOperation先执行,在执行firstOperation
[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];
输出结果:
依赖只是一种执行关系罢了,NSOperation还为我们专门提供了优先级属性,我们可以通过setQueuePriority:
方法来设置同一队列中操作的优先级,下面是系统给定的优先级(默认为NSOperationQueuePriorityNormal):
// 优先级的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
}
对于添加到队列中的操作,首先进入准备就绪的状态,然后进入就绪状态的操作的开始执行顺序由操作之间的相对的优先级决定。
就绪状态取决于操作之间的依赖关系,也就是只有这个操作的依赖操作完成了,该操作才会处于就绪状态。
举个例子:
当有四个优先级都是NSOperationQueuePriorityNormal(默认优先级)的操作:op1、op2、op3、op4,op2依赖于op3,op3依赖于op4。
queuePriority:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"begin firstOperation");
[NSThread sleepForTimeInterval:2];
NSLog(@"firstOperation end");
}];
firstOperation.queuePriority = NSOperationQueuePriorityLow;
NSBlockOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"begin secondOperation");
[NSThread sleepForTimeInterval:2];
NSLog(@"secondOperation end");
}];
secondOperation.queuePriority = NSOperationQueuePriorityHigh;
NSBlockOperation *thirdOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"begin thirdOperation");
[NSThread sleepForTimeInterval:2];
NSLog(@"thirdOperation end");
}];
thirdOperation.queuePriority = NSOperationQueuePriorityNormal;
queue.maxConcurrentOperationCount = 3;
[queue addOperation:firstOperation];
[queue addOperation:secondOperation];
[queue addOperation:thirdOperation];
输出结果:
可以通过NSOperation 的queuePriority
属性来设置操作在队列中的执行优先级:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
@property NSOperationQueuePriority queuePriority;
可以通过NSOperation 的qualityOfService
属性来设置操作在队列中的服务质量(iOS8 以后苹果推荐使用服务质量替代优先级):
typedef NS_ENUM(NSInteger, NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21,
NSQualityOfServiceUserInitiated = 0x19,
NSQualityOfServiceUtility = 0x11,
NSQualityOfServiceBackground = 0x09,
NSQualityOfServiceDefault = -1
}
@property NSQualityOfService qualityOfService;
实例:
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行任务1,%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行任务2,%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行任务3,%@",[NSThread currentThread]);
}];
// 设置优先级
op1.queuePriority = NSOperationQueuePriorityLow;
op2.queuePriority = NSOperationQueuePriorityHigh;
op3.queuePriority = NSOperationQueuePriorityNormal;
// 将操作加入队列
[queue addOperations:[NSArray arrayWithObjects:op1, op2, op3, nil] waitUntilFinished:YES];
/*
2022-08-01 15:42:45.682888+0800 NSTread[52469:2149184] 执行任务3, {number = 3, name = (null)}
2022-08-01 15:42:45.682889+0800 NSTread[52469:2149181] 执行任务1, {number = 4, name = (null)}
2022-08-01 15:42:45.682889+0800 NSTread[52469:2149180] 执行任务2, {number = 6, name = (null)}
*/
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行任务1,%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行任务2,%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行任务3,%@",[NSThread currentThread]);
}];
// 设置服务质量
op1.qualityOfService = NSQualityOfServiceBackground;
op2.qualityOfService = NSQualityOfServiceUserInitiated;
op3.qualityOfService = NSQualityOfServiceUtility;
// 将操作加入队列
[queue addOperations:[NSArray arrayWithObjects:op1, op2, op3, nil] waitUntilFinished:YES];
/*
2022-08-01 15:39:12.408928+0800 NSTread[52411:2145432] 执行任务2, {number = 7, name = (null)}
2022-08-01 15:39:12.408933+0800 NSTread[52411:2145434] 执行任务3, {number = 6, name = (null)}
2022-08-01 15:39:12.409001+0800 NSTread[52411:2145433] 执行任务1, {number = 5, name = (null)}
*/
如果我们要确保操作的执行的先后顺序,即操作间有同步关系,我们应该使用依赖关系来确保绝对的执行顺序,即使操作对象位于不同的操作队列中。在操作对象的所有依赖操作完成执行之前,操作对象不会被视为已准备好执行。
在iOS开发过程中,我们一般要在主线程中进行UI刷新,通常把一些耗时的操作放在其他线程中,当其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程间的通信:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%d--%@", i, [NSThread currentThread]);
}
// 回到主线程进行操作
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"main---%@", [NSThread currentThread]);
}];
}];
输出结果:
所以如果我们需要考虑异步操作之间的顺序行、依赖关系,比如多线程并发下载等等,就使用NSOperation。
[NSTread currentThread]
查看当前的线程。