本篇是多线程总结的第三篇,关于多线程的概念和NSThread的使用写在第一篇,《iOS多线程总结(1)——多线程相关概念及NSObject/NSThread的使用》,第二篇《iOS多线程总结(2)——GCD》主要讲解GCD的使用,本编是线程实现总结的最后一篇,主要讲解NSOperation的使用。
一. NSOperation
在MacOSXv10.6和iOS4之前,NSOperation与NSOperationQueue不同于GCD,他们使用了完全不同的机制。从MacOSXv10.6和iOS4开始,NSOperation和NSOperationQueue是建立在GCD上的。NSOperation和NSOperationQueue对比GCD会带来一点额外的系统开销,但是你可以在多个操作Operation中添加附属。你可以重用操作,取消或者暂停他们。NSOperation和 Key-Value Observation (KVO)是兼容的;例如,你可以通过监听NSNotificationCenter去让一个操作开始执行。
NSOperation调用start方法即可开始执行操作,NSOperation对象默认按同步方式执行,也就是在调用start方法的那个线程中直接执行。NSOperation对象的isConcurrent方法会告诉我们这个操作相对于调用start方法的线程,是同步还是异步执行。isConcurrent方法默认返回NO,表示操作与调用线程同步执行。
(一). NSOperation的API
@interface NSOperation : NSObject {
@private
id _private;
int32_t _private1;
#if __LP64__
int32_t _private1b;
#endif
}
// 开始操作
- (void)start;
// 操作任务的入口,一般用于自定义NSOperation的子类
- (void)main;
// 判断是否已经被取消
@property (readonly, getter=isCancelled) BOOL cancelled;
// 取消操作任务,调用后不会自动马上取消,需要通过isCancelled方法检查是否被取消,然后自己编写代码退出任务
- (void)cancel;
// 是否正在执行
@property (readonly, getter=isExecuting) BOOL executing;
// 是否执行完
@property (readonly, getter=isFinished) BOOL finished;
// 判定该线程是否是并发线程,即调用该operation的start方法的线程是否与operation所在线程相同
// 此属性即将被弃用,之后使用asynchronous属性代替
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
// 是否异步执行
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);
// 是否准备好
// 在start方法开始之前,需要确定operation是否ready,默认为YES,如果该operation没有ready,则不会start。
@property (readonly, getter=isReady) BOOL ready;
// 添加依赖关系
- (void)addDependency:(NSOperation *)op;
// 取消依赖,注意:操作对象的依赖不能在操作队列执行时取消
- (void)removeDependency:(NSOperation *)op;
// 获取有依赖关系的operation所组成的数组
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
// 优先级的枚举
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
// 优先级
@property NSOperationQueuePriority queuePriority;
// 操作完成后调用的代码块
@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);
// 是否等待operation执行结束,如果为YES,会堵塞当前线程,直到operation执行结束,才会执行接下来的代码
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);
// 设定operation的线程优先级,线程优先级默认为0.5,取值范围0~1
// 即使设定了线程优先级,也只能保证其在main方法范围内有效,operation的其他代码仍然执行在默认线程
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);
// 用于系统自动合理的管理队列的资源分配
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
// 操作任务的名字
@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);
@end
(二). Quality of Service(QoS)
这是在iOS8之后提供的新功能,苹果提供了几个Quality of Service枚举来使用:user interactive, user initiated, utility 和 background,通过这些枚举告诉系统我们在进行什么样的工作,然后系统会通过合理的资源控制来最高效的执行任务代码,其中主要涉及到CPU调度的优先级、IO优先级、任务运行在哪个线程以及运行的顺序等等,我们可以通过一个抽象的Quality of Service枚举参数来表明任务的意图以及类别。
NSQualityOfServiceUserInteractive
与用户交互的任务,这些任务通常跟UI级别的刷新相关,比如动画,这些任务需要在一瞬间完成.NSQualityOfServiceUserInitiated
由用户发起的并且需要立即得到结果的任务,比如滑动scroll view时去加载数据用于后续cell的显示,这些任务通常跟后续的用户交互相关,在几秒或者更短的时间内完成NSQualityOfServiceUtility
一些可能需要花点时间的任务,这些任务不需要马上返回结果,比如下载的任务,这些任务可能花费几秒或者几分钟的时间NSQualityOfServiceBackground
这些任务对用户不可见,比如后台进行备份的操作,这些任务可能需要较长的时间,几分钟甚至几个小时NSQualityOfServiceDefault
优先级介于user-initiated 和 utility,当没有 QoS信息时默认使用,开发者不应该使用这个值来设置自己的任务
二. NSBlockOperation
(一). 知识点
如果是通过start方法启动操作,第一个操作不管是用blockOperationWithBlock:添加或者用addExecutionBlock:方法添加,第一个操作默认在主线程上执行,其余的操作会开辟新线程执行操作。
如果通过completionBlock属性添加操作,系统会把completionBlock属性上的任务直接添加到Operation开辟的最后一条线程上;也就是说,创建一个NSOperation对象,然后往里面添加多个任务,当启动时,开辟多线程,completionBlock属性上的任务就会添加到开启的多个线程中的最后开启的那条线程上。
一旦Operation添加到操作队列queue中,queue就拥有了这个Operation对象并且不能被删除,唯一能做的事情是取消;通过cancel方法取消任务,Operation不会马上被取消,只能通过[op isCancelled]自己检测是否被取消,然后编写退出操作的代码。
如果需要在当前线程中处理operation完成后的结果,可以使用NSOperation的waitUntilFinished方法阻塞当前线程,等待operation完成。通常我们应该避免编写这样的代码,阻塞当前线程可能是一种简便的解决方案,但是它引入了更多的串行代码,限制了整个应用的并发性,同时也降低了用户体验。绝对不要在应用主线程中等待一个Operation,只能在第二或次要线程中等待。阻塞主线程将导致应用无法响应用户事件,应用也将表现为无响应。
(二). NSBlockOperation的API
@interface NSBlockOperation : NSOperation {
@private
id _private2;
void *_reserved2;
}
// 添加操作任务
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
// 添加操作任务
- (void)addExecutionBlock:(void (^)(void))block;
// 获取所有的操作任务所组成的数组
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;
@end
(三). NSBlockOperation的使用
1. 不使用NSOperationQueue
// 使用blockOperationWithBlock快速创建操作对象
// NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
//
// NSLog(@"---%@", [NSThread currentThread]);
// }];
// 使用传统的方法创建操作对象
NSBlockOperation *op = [[NSBlockOperation alloc] init];
// 创建一个NSOperation对象,然后往里面添加多个任务,当启动时,开辟多线程,
// completionBlock属性上的任务就会添加到开启的多个线程中的最后开启的那条线程上
op.completionBlock = ^{
NSLog(@"---%@", [NSThread currentThread]);
};
// 添加操作
// 如果是通过start方法启动操作,第一个操作不管是用blockOperationWithBlock:添加或者用addExecutionBlock:方法添加,
// 第一个操作默认在主线程上执行,其余的操作会开辟新线程执行操作
[op addExecutionBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
// 开始操作任务
[op start];
2. completionBloc属性与NSOperationQueue
NSBlockOperation *op1 = [[NSBlockOperation alloc] init];
NSBlockOperation *op2 = [[NSBlockOperation alloc] init];
NSBlockOperation *op3 = [[NSBlockOperation alloc] init];
op1.completionBlock = ^{
NSLog(@"---%@", [NSThread currentThread]);
};
op2.completionBlock = ^{
NSLog(@"---%@", [NSThread currentThread]);
};
op3.completionBlock = ^{
NSLog(@"---%@", [NSThread currentThread]);
};
// 会开辟多条线程并运行并执行completionBlock属性中得操作
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
3. 使用NSOperationQueue
NSBlockOperation *op1 =[NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 10; i++) {
NSLog(@"op----%d----%@", i, [NSThread currentThread]);
}
}];
NSBlockOperation *op2 =[NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 10; i++) {
NSLog(@"op2----%d----%@", i, [NSThread currentThread]);
}
}];
NSBlockOperation *op3 =[NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 10; i++) {
NSLog(@"op3----%d----%@", i, [NSThread currentThread]);
}
}];
// 添加依赖
[op1 addDependency:op3];
[op1 addDependency:op2];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
NSLog(@"%@",[op1 dependencies]);
4. 取消Operation
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSBlockOperation *op;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_op = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5000; i++) {
// 判断是否被取消
if ([_op isCancelled]) {
NSLog(@"----op isCancelled----");
break;
}
NSLog(@"%d----%@", i, [NSThread currentThread]);
}
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:_op];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[_op cancel];
}
三. NSInvocationOperation
(一). NSInvocationOperation的API
@interface NSInvocationOperation : NSOperation {
@private
id _inv;
id _exception;
void *_reserved2;
}
// 初始化Operation
- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
// 使用NSInvocation对象初始化Operation
- (instancetype)initWithInvocation:(NSInvocation *)inv NS_DESIGNATED_INITIALIZER;
// 获取NSInvocation对象
@property (readonly, retain) NSInvocation *invocation;
// 获取操作的返回值
// 注意:方法的返回值必须是对象才能获取正确,不能是数值类型,且必须操作Operation完成才可以获取
@property (nullable, readonly, retain) id result;
@end
(二). NSInvocationOperation的使用
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self InvocationOperation1];
NSLog(@"-------------------打印结果分割线----------------------");
[self InvocationOperation2];
}
#pragma mark - 基本使用
- (void)InvocationOperation1 {
// 创建方式一
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
[op1 start];
// 创建方式二
// 根据方法来初始化方法签名对象,NSMethodSignature:方法签名类
NSMethodSignature *sig = [ViewController instanceMethodSignatureForSelector:@selector(run:two:)];
// 根据方法签名对象来创建一个调用对象,NSInvocation:调用类
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
// 设置调用者
invocation.target = self;
// 设置调用方法
invocation.selector = @selector(run:two:);
// 设置参数
int value1 = 100;
int value2 = 200;
[invocation setArgument:&value1 atIndex:2];
[invocation setArgument:&value2 atIndex:3];
// 根据方法调用对象初始化Operation对象
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithInvocation:invocation];
[op2 start];
// 获取OP2的返回值
// 注意:方法的返回值必须是对象才能获取正确,不能是数值类型,且必须操作Operation完成才可以获取
NSLog(@"OP2的返回值:%@", op2.result );
}
- (void)run {
NSLog(@"run ---- 线程%@", [NSThread currentThread]);
}
- (NSNumber *)run:(int)value1 two:(int)value2 {
NSLog(@"run:two: --- %d --- %d --- %@", value1, value2, [NSThread currentThread]);
return @(value1 + value2);
}
#pragma mark - 配合NSOperationQueue使用
- (void)InvocationOperation2 {
// 1. 创建操作队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2. 创建操作方法
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
NSMethodSignature *sig1 = [[self class] instanceMethodSignatureForSelector:@selector(run:two:)];
NSInvocation *invocation1 = [NSInvocation invocationWithMethodSignature:sig1];
invocation1.target = self;
invocation1.selector = @selector(run:two:);
int value1 = 100;
int value2 = 200;
[invocation1 setArgument:&value1 atIndex:2];
[invocation1 setArgument:&value2 atIndex:3];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithInvocation:invocation1];
NSMethodSignature *sig2 = [[self class] instanceMethodSignatureForSelector:@selector(run:two:)];
NSInvocation *invocation2 = [NSInvocation invocationWithMethodSignature:sig2];
invocation2.target = self;
invocation2.selector = @selector(run:two:);
int value3 = 400;
int value4 = 550;
[invocation2 setArgument:&value3 atIndex:2];
[invocation2 setArgument:&value4 atIndex:3];
NSInvocationOperation *op4 = [[NSInvocationOperation alloc] initWithInvocation:invocation2];
// 3. 添加操作任务
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
}
@end
输出结果:
四. 自定义NSOperation子类
如果NSInvocationOperation和NSBlockOperation对象不能满足需求, 可以直接继承NSOperation, 并添加任何你想要的行为。继承所需的工作量主要取决于你要实现非并发还是并发的NSOperation。定义非并发的NSOperation要简单许多,只需要重载-(void)main这个方法,在这个方法里面执行主任务,并正确地响应取消事件; 当要通过cancel取消操作时,它不会马上就取消,它会在未来的某个时候在“main”函数中明确地检查isCancelled==YES时被取消掉;否则,操作会一直执行到完成为止。
如果需要创建一个并发的Operation,并手动执行,则需要重写main,start,isCancelled,isReady,isConcurrent,isFinished,isExcluting,dependency,queuepripority方法,并维护每个property的KVO,同时确保每个property的原子性。
非并发的实现步骤:
- 继承NSOperation类
- 重写main()方法
- 在main()方法中创建一个@autoreleasepool
- 将要做的操作放在@autoreleasepool中
提示:创建自己的自动释放池的原因是,因为我们不能访问主线程的自动释放池,所以应该自己创建一个。
非并发的实现代码:
#import "SJMSerialOperation.h"
@implementation SJMSerialOperation
- (void)main {
@autoreleasepool {
for (int i = 0; i < 100; i++) {
NSLog(@"%i --- %@", i, [NSThread currentThread]);
}
// 被取消时退出
if (self.isCancelled == YES) return;
for (int i = 0; i < 100; i++) {
NSLog(@"%i --- %@", i, [NSThread currentThread]);
}
// 被取消时退出
if (self.isCancelled == YES) return;
}
}
@end
#import "ViewController.h"
#import "SJMSerialOperation.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
SJMSerialOperation *op = [[SJMSerialOperation alloc] init];
[op start];
SJMSerialOperation *op1 = [[SJMSerialOperation alloc] init];
SJMSerialOperation *op2 = [[SJMSerialOperation alloc] init];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op1];
[queue addOperation:op2];
}
@end
五. NSOperationQueue
(一). 知识点
NSOperationQueue称为操作队列,它的作用是管理子线程的生命周期和线程同步问题,有了操作队列,我们只需要关注具体的操作任务,而不用去管线程的生命周期和线程同步问题等问题,大大提高了开发效率。
只要在操作队列中加入操作任务,程序运行时它就会去检查maxConcurrentOperationCount(最大并发数属性),如果值是1,它就相当于串行队列,只开辟一条线程,如果大于1,就根据值开辟相应条数的子线程,当任务操作完后回自动销毁线程。
当通过设置suspended属性来暂停操作队列时,队列不会马上暂停,系统会执行完当前正在执行的任务才暂停队列。
一旦Operation对象添加到操作队列就不能被删除,只能通过Operation对象方法cancel取消;取消全部操作队列中的任务的方法是cancelAllOperations;但要成功取消,必须自己编写监听代码和取消代码,使用NSOperation对象的isCancelled方法检查是否被取消,如果被取消了,可以通过return等方法退出任务。
(二). NSOperationQueue的API
// 默认最大操作数为-1
static const NSInteger NSOperationQueueDefaultMaxConcurrentOperationCount = -1;
@interface NSOperationQueue : NSObject {
@private
id _private;
void *_reserved;
}
// 添加操作对象(NSOperation对象)
- (void)addOperation:(NSOperation *)op;
// 添加操作对象组(NSOperation对象),waitUntilFinished:是否阻塞当前线程,等待所有操作都完成
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);
// 添加操作任务
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);
// 获取操作任务对象组
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
// 获取操作任务对象总数
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
// 最大并发数
@property NSInteger maxConcurrentOperationCount;
// 是否暂停队列
@property (getter=isSuspended) BOOL suspended;
// 队列名字
@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);
// 用于系统自动合理的管理队列的资源分配
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
// underlyingQueue属性的值是主线程的调度队列,此属性不能设置为其它值
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);
// 取消所有操作任务,配合NSOperation对象的isCancelled方法使用
- (void)cancelAllOperations;
// 阻塞当前线程,等待所有操作执行完毕
- (void)waitUntilAllOperationsAreFinished;
// 获取当前操作队列
+ (nullable NSOperationQueue *)currentQueue NS_AVAILABLE(10_6, 4_0);
// 获取主操作队列
+ (NSOperationQueue *)mainQueue NS_AVAILABLE(10_6, 4_0);
@end
(三). 使用方法
1. NSOperationQueue单独使用
NSOperationQueue可以单独使用,不用自己创建NSOperation对象,可以直接通过addOperationWithBlock:方法往操作队列添加一个任务,它的底层会自动创建一个NSBlockOperation对象,再通过addOperation:方法把NSBlockOperation对象加入到操作队列中。
代码举例:
- 继承NSOperationQueue,创建一个子类SJMOperationQueue,重写addOperationWithBlock:和addOperation:方法
#import "SJMOperationQueue.h"
@implementation SJMOperationQueue
-(void)addOperationWithBlock:(void (^)(void))block {
NSLog(@"%s",__func__);
[super addOperationWithBlock:block];
}
- (void)addOperation:(NSOperation *)op {
NSLog(@"%s",__func__);
[super addOperation:op];
}
@end
- 使用addOperationWithBlock:方法直接添加任务
#import "ViewController.h"
#include "SJMOperationQueue.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self operationQueue];
}
- (void)operationQueue {
SJMOperationQueue *queue = [[SJMOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSLog(@"-----%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"+++++%@",[NSThread currentThread]);
}];
NSLog(@"%@",queue.operations);
}
@end
打印结果:
2. NSOperationQueue配合NSOperation的子类使用
- 自定义NSOperation的子类
#import "SJMNSOperation.h"
@implementation SJMNSOperation
- (void)main {
for (int i = 0; i < 1000; i++) {
NSLog(@"op3---1---%@", [NSThread currentThread]);
}
}
@end
- 自定义NSOperation的子类的使用
#import "ViewController.h"
#include "SJMNSOperation.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
// 1. 创建操作队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2. 添加操作任务
// 2.1 通过NSInvocationOperation创建操作任务
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// 2.2 通过NSBlockOperation创建操作任务
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
// 2.3 通过自定义的NSOperation子类创建操作任务
SJMNSOperation *op3 = [[SJMNSOperation alloc] init];
// 3. 添加到操作队列
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
- (void)run {
NSLog(@"%@",[NSThread currentThread]);
}
3. 取消队列中的所有操作
#import "ViewController.h"
#include "SJMOperationQueue.h"
#include "SJMNSOperation.h"
@interface ViewController ()
@property (nonatomic, strong) NSOperationQueue *queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建操作队列
_queue = [[NSOperationQueue alloc] init];
// 添加操作任务
[self operationQueue2];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 取消所有操作任务
[_queue cancelAllOperations];
}
#pragma mark - 取消操作队列里所有的任务
- (void)operationQueue2 {
// 通过自定义的NSOperation子类创建操作任务
SJMNSOperation *op1 = [[SJMNSOperation alloc] init];
SJMNSOperation *op2 = [[SJMNSOperation alloc] init];
SJMNSOperation *op3 = [[SJMNSOperation alloc] init];
SJMNSOperation *op4 = [[SJMNSOperation alloc] init];
SJMNSOperation *op5 = [[SJMNSOperation alloc] init];
// 添加到操作队列
[_queue addOperation:op1];
[_queue addOperation:op2];
[_queue addOperation:op3];
[_queue addOperation:op4];
[_queue addOperation:op5];
}