1.先来理解几个概念
NSOperation:字面意思:操作。
操作就是要去“去执行一件事情”这个过程,,这个事情可以有一个任务,也可以有多个任务,比如在NSBlockOperation 可以通过 addExecutionBlock 来追加需要操作的任务。在 iOS中,NSOperation 是一个抽象化的类,我们需要使用 NSOperation 的具体化子类来进行使用,其中包括 NSInvocationOperation、NSBlockOperation,也可以自定义 NSOperation 的子类去使用。
NSOperationQueue: 操作队列。
在 iOS系统中有两种操作队列,一种是系统提供的主操作队列,主队列运行在主线程中,不用开发人员创建,获取方式是通过[NSOperationQueue mainQueue]获取,另一种是我们自己创建的操作队列。
操作队列相当于一个存放操作的容器,我们创建容器,然后再将创建好的操作放入这个容器中,然后系统开始执行操作。
2.如何用NSOperation/NSOperationQueue实现多线程编程
NSOperation/NSOperationQueue实现多线程编程总共分为三步。
第一步,创建一个容器,也就是创建操作队列。
NSOperationQueue *operationQueue = [[NSOperationQueue alloc]init];
第二步,创建要执行的操作。
第三步,将创建好的操作加入到操作队列中。
3.NSOperation的使用
单独使用操作时,需要对操作对象调用 star 来时操作开始执行。
3.1 在主线程使用 NSInvocationOperation
- (void)invocationOperationOnMainThread {
NSInvocationOperation *invacationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doWork) object:nil];
[invacationOperation start];
}
...
- (void)doWork {
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"A ----- %d -----%@",i,[NSThread currentThread]);
}
}
控制台输出:
说明操作是在当前主线程执行的,没有开启新线程去执行操作。
3.2 在分线程使用 NSInvocationOperation
[NSThread detachNewThreadSelector:@selector(invocationOperationOnOtherThread) toTarget:self withObject:nil ];
···
- (void)invocationOperationOnOtherThread {
NSLog(@"--- begin ---");
NSInvocationOperation *invacationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doWork) object:nil];
[invacationOperation start];
NSLog(@"--- end ---");
}
控制台输出:
说明在其他线程中使用子类 NSInvocationOperation 时,操作是在当前调用的其他线程执行的,也并没有开启新线程。
3.3 在主线程使用 NSBlockOperation
在主线程执行一个 NSBlockOperation 时:
- (void)blockOperationOnMainThread {
NSLog(@"--- begin ---");
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"A ----- %d ----- %@",i,[NSThread currentThread]);
}
}];
[blockOperation start];
NSLog(@"--- end ---");
}
控制台输出:
说明在主线程中单独使用 NSBlockOperation 执行一个操作的情况下,操作是在当前主线程执行的,并没有开启新线程。
如果我在后面追加几个操作呢?
#pragma mark - 主线程使用 NSBlockOperation
- (void)blockOperationOnMainThread {
NSLog(@"--- begin ---");
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"A ----- %d ----- %@",i,[NSThread currentThread]);
}
}];
//如果不添加下面的多个 ExecutionBlock 任务,那么任务A 都是在主线程执行,如果添加了多个任务,那么后面添加的部分任务也是会在分线程中执行的。
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"B -----%d ----- %@",i,[NSThread currentThread]);
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"C ----- %d -----%@",i,[NSThread currentThread]);
}
}];
[blockOperation start];
NSLog(@"--- end ---");
}
控制台输出:
说明当我们添加多个NSBlockOperation的话,系统会自动开辟多个线程去执行操作,具体是在主线程还是在分线程执行block中的操作,由系统决定。
3.4 在分线程使用 NSBlockOperation
[NSThread detachNewThreadSelector:@selector(blockOperationOnOtherThread) toTarget:self withObject:nil];
···
#pragma mark - 分线程使用 NSBlockOperation
- (void)blockOperationOnOtherThread {
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"A ----- %d ----- %@",i,[NSThread currentThread]);
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"B -----%d ----- %@",i,[NSThread currentThread]);
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"C ----- %d -----%@",i,[NSThread currentThread]);
}
}];
[blockOperation start];
}
控制台输出:
说明在分线程执行多个 NSBlockOperation的话,系统会根据多个操作任务自动开启多个线程进行异步操作。
3.5 操作和操作队列配和使用实现多线程异步执行
- (void)creatOperationQueueAddOperation {
//创建操作队列 OperationQueue
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
//创建操作 Operation 如果只有doWork 任务也是在分线程执行的。
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doWork) object:nil];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"B ----- %d -----%@",i,[NSThread currentThread]);
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"C ----- %d -----%@",i,[NSThread currentThread]);
}
}];
//可使用 addOperationWithBlock 直接添加操作任务
[operationQueue addOperationWithBlock:^{
for (int i = 0; i < 2; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"D ----- %d -----%@",i,[NSThread currentThread]);
}
}];
[operationQueue addOperation:invocationOperation];//addOperation 操作会将 operation自动 star
[operationQueue addOperation:blockOperation];
}
控制台输出:
操作的执行在 begin 和 end 之后,使用 NSOperation 子类创建操作,并使用 addOperation: 将操作加入到操作队列后能够实现操作的并发执行。
addOperation 操作会将 operation自动 star
可使用 addOperationWithBlock 直接往操作队列添加操作任务。addOperationWithBlock 相当于直接添加了一个 NSBlockOperation 操作。
3.6 操作间调度/通信
使用场景:开启一个操作队列去执行复杂而且耗时的操作,然后调度到主队列更新UI
#pragma mark - 线程间通信 调度
- (void)operationCommunicate {
NSOperationQueue *queue =[[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"C ---%@", [NSThread currentThread]);
}
//调度到主线程执行 比如UI刷新
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"主线程执行任务 %d ---%@", i,[NSThread currentThread]);
}
}];
}];
}
控制台输出:
3.7 设置最大并发数控制串行/并发 setMaxConcurrentOperationCount
#pragma mark - 使用 maxConcurrentOperationCount (最大并发数)参数设置串行/并发
- (void)exchangeSerialOrConcurrentQueueWithMaxConcurrentCount {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// queue.maxConcurrentOperationCount = 0; //不会执行
// queue.maxConcurrentOperationCount = -1; //默认为-1,直接开启并发队列
// queue.maxConcurrentOperationCount = 1;//串行队列
// queue.maxConcurrentOperationCount = 2;//两个并发
// queue.maxConcurrentOperationCount = 4;//并发队列
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"A---%@", [NSThread currentThread]);
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"B---%@", [NSThread currentThread]);
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"C---%@", [NSThread currentThread]);
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"D---%@", [NSThread currentThread]);
}
}];
}
当 queue.maxConcurrentOperationCount = 1 时,控制台输出为:
当 queue.maxConcurrentOperationCount = 2 时,控制台输出为:
可见设置不同的 maxConcurrentOperationCount,可以实现任务执行的串行和并发控制。
另外**设置最大并发操作的数量并不等于开辟线程的数量。具体开启几条线程去执行是由系统控制的。**
3.8 操作依赖 addDependency
#pragma mark - 操作依赖addDependency
- (void) addOperationDependency {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doWork) object:nil];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"B---%@", [NSThread currentThread]);
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"B addExecution ---%@", [NSThread currentThread]);
}
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"C ---%@", [NSThread currentThread]);
}
}];
//依赖关系
//invocationOperation --》 blockOperation --》 blockOperation2
[invocationOperation addDependency:blockOperation];//让invocationOperation 依赖于 blockOperation,则先执行blockOperation,在执行invocationOperation
[blockOperation addDependency:blockOperation2];
[queue addOperation:invocationOperation];
[queue addOperation:blockOperation];
[queue addOperation:blockOperation2];
// [queue addOperations:@[invocationOperation,blockOperation,blockOperation2] waitUntilFinished:NO];
}
控制台输出:
添加依赖关系依赖关系 invocationOperation --》 blockOperation --》 blockOperation2 后,执行顺序为 blockOperation2 --》blockOperation --》 invocationOperation。
3.9 操作优先级 setQueuePriority
我们在 addOperationDependency 方法中,对 invocationOperation 设置优先级 为 NSOperationQueuePriorityHigh,当执行后发现执行顺序没有变化, 说明存在依赖关系的操作任务,并不会因为优先级的改变而改变执行顺序,优先级的设置不能取代打破/依赖关系。
4. 线程安全/数据安全
还是想 GCD 中买票的情景: 对 NSOperationQueue 加锁,可以保证线程安全和数据安全。加锁方式和GCD方式类似,可以使用信号量添加自旋锁,可以使用synchronized添加自旋锁,也可以使用NSLock。
#pragma mark - 线程安全数据安全
- (void)initTicketStatusSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"semaphore---begin");
__weak __typeof(self)weakSelf = self;
self.ticketSurplusCount = 5;
// queue1 代表北京火车票售卖窗口的操作队列
NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
queue1.maxConcurrentOperationCount = 1;
// queue2 代表上海火车票售卖窗口的操作队列
NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
queue2.maxConcurrentOperationCount = 1;
//创建买票的操作
NSBlockOperation *shanghaiOperation = [NSBlockOperation blockOperationWithBlock:^{
[weakSelf saleTicketSafe];
}];
NSBlockOperation *beijingOperation = [NSBlockOperation blockOperationWithBlock:^{
[weakSelf saleTicketSafe];
}];
//将操作添加进操作队列
[queue1 addOperation:shanghaiOperation];
[queue2 addOperation:beijingOperation];
}
- (void)saleTicketSafe {
// while (1) {
// // 相当于加锁
// dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
// if (self.ticketSurplusCount > 0) { //如果还有票,继续售卖
// self.ticketSurplusCount--;
// NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", _ticketSurplusCount, [NSThread currentThread]]);
// [NSThread sleepForTimeInterval:0.2];
// } else { //如果已卖完,关闭售票窗口
// NSLog(@"所有火车票均已售完");
// dispatch_semaphore_signal(_semaphore);
// break;
// }
// dispatch_semaphore_signal(_semaphore);
// }
//NSLock
// while (1) {
// // 加锁
// [self.lock lock];
// if (self.ticketSurplusCount > 0) {
// //如果还有票,继续售卖
// self.ticketSurplusCount--;
// NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
// [NSThread sleepForTimeInterval:0.2];
// }
// // 解锁
// [self.lock unlock];
//
// if (self.ticketSurplusCount <= 0) {
// NSLog(@"所有火车票均已售完");
// break;
// }
// }
//互斥锁
while (1) {
// 相当于加锁
@synchronized(self) {
if (self.ticketSurplusCount > 0) { //如果还有票,继续售卖
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", _ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { //如果已卖完,关闭售票窗口
NSLog(@"所有火车票均已售完");
break;
}
}
}
}
控制台输出:
5.NSOperation/NSOperationQueue 和 GCD 的区别
-
GCD是底层的C语言构成的API,而NSOperationQueue 是基于GCD封装而成,其相关对象是Objc的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;而Operation作为一个对象,为我们提供了更多的选择;
-
NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行;
-
NSOperation 暴露出很多属性和API。 我们可以用
- (void)cancel;取消操作;
- (BOOL)isFinished 判断操作是否已经结束;
- (BOOL)isCancelled 判断操作是否已经标记为已取消;
- (BOOL)isExecuting 判断操作是否正在在运行;
- (BOOL)isReady 判断操作是否处于准备就绪状态。
所以通过 KVO 的方式,我们可以很方便的知道操作NSOperation 的执行状态,也就是中间态。我们也可以随时取消已经设定要将要准备执行的任务,而GCD没法停止已经加入queue block 中的任务。
注:cancel 方法调用时,已经开始的操作任务就无法阻止了,只能阻止操作队列中下一个准备就绪将要被执行的操作。
-
在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行。
-
如果任务之间不太互相依赖,那么我们可以优先使用GCD,如果任务之间有依赖 或者要监听任务的执行情况,那么我们可以优先使用NSOperationQueue。