今天看啥  ›  专栏  ›  上山打逗比

iOS 多线程(NSOperation使用指南)

上山打逗比  · 掘金  ·  · 2020-01-29 10:12
阅读 4

iOS 多线程(NSOperation使用指南)

NSOperation简介

NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高

为什么要使用 NSOperation、NSOperationQueue?

  • 可添加完成的代码块,在操作完成后执行。
  • 添加操作之间的依赖关系,方便的控制执行顺序。
  • 设定操作执行的优先级。
  • 可以很方便的取消一个操作的执行。
  • 使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。

NSOperation、NSOperationQueue 操作和操作队列

既然是基于 GCD 的更高一层的封装。那么,GCD 中的一些概念同样适用于 NSOperation、NSOperationQueue。在 NSOperation、NSOperationQueue 中也有类似的任务(操作)和队列(操作队列)的概念。

  • 操作(Operation
    • 执行操作的意思,换句话说就是你在线程中执行的那段代码。
    • 在 GCD 中是放在 block 中的。在 NSOperation 中,我们使用 NSOperation 子类NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。
  • 操作队列(Operation Queues)
    • 这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
    • 操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发、串行。
    • NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。
  • NSOperation、NSOperationQueue 使用步骤
    • NSOperation 需要配合 NSOperationQueue 来实现多线程。因为默认情况下,NSOperation 单独使用时系统同步执行操作,配合 NSOperationQueue 我们能更好的实现异步执行。
    • NSOperation 实现多线程的使用步骤分为三步
      • 创建操作:先将需要执行的操作封装到一个 NSOperation 对象中。
      • 创建队列:创建 NSOperationQueue 对象。
      • 将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中。
      • 之后呢,系统就会自动将 NSOperationQueue 中的 NSOperation 取出来,在新线程中执行操作。

NSOperation 和 NSOperationQueue 基本使用

NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作。我们有三种方式来封装操作。

  • 使用子类 NSInvocationOperation
  • 使用子类 NSBlockOperation
  • 自定义继承 NSOperation 的子类,通过实现内部相应的方法来封装操作。

使用子类 NSInvocationOperation

/**
 NSInvocationOperation : 创建操作 ---> 创建队列 ---> 操作加入队列
 */
- (void)demo{
    //1:创建操作
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"123"];
    //2:创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //3:操作加入队列 --- 操作会在新的线程中
    [queue addOperation:op];
}

- (void)handleInvocation:(id)op{
    NSLog(@"%@ --- %@",op,[NSThread currentThread]);
}
复制代码

打印:

2020-01-29 15:43:11.012492+0800 001---NSOperation初体验[1628:78932] 123 --- <NSThread: 0x600001ea9080>{number = 3, name = (null)}
复制代码

NSInvocationOperation也可以手动调起,这样不会开启线程,会在当前队列执行任务

/**
 手动调起操作
 */
- (void)demo1{
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"123"];
    // 注意 : 如果该任务已经添加到队列,你再手动调回出错
    // NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // [queue addOperation:op];
    // 手动调起操作,默认在当前线程
    [op start];
}

- (void)handleInvocation:(id)op{
    NSLog(@"%@ --- %@",op,[NSThread currentThread]);
}
复制代码

打印:

2020-01-29 15:52:55.404726+0800 001---NSOperation初体验[1740:83188] 123 --- <NSThread: 0x60000024e940>{number = 1, name = main}
复制代码

注意:我们尽量不要用这种方式去执行任务,如果该任务已经添加到队列,再手动调起任务程序会崩溃

使用子类 NSBlockOperation

/**
 blockOperation 初体验
 */
- (void)demo2{
 
    //1:创建blockOperation
    NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@ -- %@",[NSThread mainThread],[NSThread currentThread]);
    }];
    //1.1 设置监听
    bo.completionBlock = ^{
        NSLog(@"完成了!!!");
    };
    //2:创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //3:添加到队列
    [queue addOperation:bo];
}
复制代码

打印:

2020-01-29 15:57:30.760964+0800 001---NSOperation初体验[1812:85696] <NSThread: 0x600001a66940>{number = 1, name = (null)} -- <NSThread: 0x600001a2dec0>{number = 3, name = (null)}
2020-01-29 15:57:30.761098+0800 001---NSOperation初体验[1812:85697] 完成了!!!
复制代码

NSBlockOperation也可以使用start方法手动执行,这样不会开启线程,会在当前队列执行任务。如果该任务已经添加到队列中,手动调起任务程序会崩溃

自定义继承 NSOperation 的子类,通过实现内部相应的方法来封装操作。

如果使用子类 NSInvocationOperation、NSBlockOperation 不能满足日常需求,我们可以使用自定义继承自 NSOperation 的子类。

先定义一个继承 NSOperation 的子类,重写main方法。

// NHOperation.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NHOperation : NSOperation

@end

NS_ASSUME_NONNULL_END

// NHOperation.m
#import "NHOperation.h"

@implementation NHOperation

- (void)main{
    
    if (!self.isCancelled) {
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@", [NSThread currentThread]);
        }
    }
}
@end
复制代码

导入头文件NHOperation.h

- (void)demo3{
    NHOperation *nhQueue = [[NHOperation alloc]init];
    [nhQueue start];
}
复制代码

打印:

2020-01-29 16:20:57.036713+0800 001---NSOperation初体验[2060:94891] 1---<NSThread: 0x600001c16940>{number = 1, name = main}
2020-01-29 16:20:59.037879+0800 001---NSOperation初体验[2060:94891] 1---<NSThread: 0x600001c16940>{number = 1, name = main}
复制代码

这种自定义继承NSOperation的写法可以自己掌握线程的生命周期,SDWebImage框架重写了start,main等一系列方法,去实现下载图片模块

可执行代码块

/**
 可执行代码块
 */
- (void)demo5{
    //创建操作
    NSBlockOperation *ob = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    //添加执行代码块
    [ob addExecutionBlock:^{
        NSLog(@"这是一个执行代码块 - %@",[NSThread currentThread]);
    }];
    // 执行代码块 在新的线程  创建的操作在当前线程
    //[ob start];
    
    //利用队列,两个都在新的线程中
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:ob];
    
}
复制代码

打印:

2020-01-29 16:37:49.565971+0800 001---NSOperation初体验[2207:101558] <NSThread: 0x600002457d40>{number = 3, name = (null)}
2020-01-29 16:37:49.565991+0800 001---NSOperation初体验[2207:101555] 这是一个执行代码块 - <NSThread: 0x600002458480>{number = 4, name = (null)}
复制代码

线程间通讯

/**
 线程通讯
 */
- (void)demo6{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"Noah";
    [queue addOperationWithBlock:^{
        NSLog(@"%@ = %@",[NSOperationQueue currentQueue],[NSThread currentThread]);
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"%@ --%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
        }];
    }];
}
复制代码

打印:

2020-01-29 16:38:41.372075+0800 001---NSOperation初体验[2239:102618] <NSOperationQueue: 0x6000015ee560>{name = 'Noah'} = <NSThread: 0x6000000f9880>{number = 3, name = (null)}
2020-01-29 16:38:41.378217+0800 001---NSOperation初体验[2239:102581] <NSOperationQueue: 0x6000015ea180>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600000086940>{number = 1, name = main}
复制代码

优先级设定

/**
 优先级,只会让CPU有更高的几率调用,不是说设置高就一定全部先完成
 */
- (void)demo4{
    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"第1个操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    // 设置优先级 - 最高
    bo1.qualityOfService = NSQualityOfServiceUserInteractive;
    
    //创建第二个操作
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"第二个操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    // 设置优先级 - 最低
    bo2.qualityOfService = NSQualityOfServiceBackground;
    
        //2:创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //3:添加到队列
    [queue addOperation:bo1];
    [queue addOperation:bo2];
}
复制代码

打印:

2020-01-29 16:42:21.830886+0800 001---NSOperation初体验[2296:104794] 第1个操作 0 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.830901+0800 001---NSOperation初体验[2296:104795] 第2个操作 0 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831015+0800 001---NSOperation初体验[2296:104794] 第1个操作 1 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831076+0800 001---NSOperation初体验[2296:104794] 第1个操作 2 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831107+0800 001---NSOperation初体验[2296:104795] 第2个操作 1 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831135+0800 001---NSOperation初体验[2296:104794] 第1个操作 3 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831217+0800 001---NSOperation初体验[2296:104794] 第1个操作 4 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831241+0800 001---NSOperation初体验[2296:104795] 第2个操作 2 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831306+0800 001---NSOperation初体验[2296:104794] 第1个操作 5 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831351+0800 001---NSOperation初体验[2296:104795] 第2个操作 3 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831387+0800 001---NSOperation初体验[2296:104794] 第1个操作 6 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831438+0800 001---NSOperation初体验[2296:104795] 第2个操作 4 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831595+0800 001---NSOperation初体验[2296:104794] 第1个操作 7 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831771+0800 001---NSOperation初体验[2296:104794] 第1个操作 8 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831957+0800 001---NSOperation初体验[2296:104795] 第2个操作 5 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.834469+0800 001---NSOperation初体验[2296:104794] 第1个操作 9 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.834487+0800 001---NSOperation初体验[2296:104795] 第2个操作 6 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.834579+0800 001---NSOperation初体验[2296:104795] 第2个操作 7 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.834659+0800 001---NSOperation初体验[2296:104795] 第2个操作 8 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.834761+0800 001---NSOperation初体验[2296:104795] 第2个操作 9 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
复制代码

设置队列最大并发数

- (void)demo7{
    
    NSOperationQueue *queue = [NSOperationQueue new];
    // 设置队列最大并发数为2
    queue.maxConcurrentOperationCount = 2;
    for (int i = 0; i<10; i++) {
        [queue addOperationWithBlock:^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        }];
    }
}
复制代码

打印:

2020-01-29 16:45:11.512510+0800 001---NSOperation初体验[2360:107175] 0-<NSThread: 0x6000023c2140>{number = 3, name = (null)}
2020-01-29 16:45:11.512505+0800 001---NSOperation初体验[2360:107173] 1-<NSThread: 0x6000023d1280>{number = 4, name = (null)}
2020-01-29 16:45:13.514845+0800 001---NSOperation初体验[2360:107172] 2-<NSThread: 0x6000023c4980>{number = 5, name = (null)}
2020-01-29 16:45:13.514883+0800 001---NSOperation初体验[2360:107174] 3-<NSThread: 0x6000023fc380>{number = 6, name = (null)}
2020-01-29 16:45:15.516537+0800 001---NSOperation初体验[2360:107173] 4-<NSThread: 0x6000023d1280>{number = 4, name = (null)}
2020-01-29 16:45:15.516537+0800 001---NSOperation初体验[2360:107172] 5-<NSThread: 0x6000023c4980>{number = 5, name = (null)}
2020-01-29 16:45:17.518188+0800 001---NSOperation初体验[2360:107173] 7-<NSThread: 0x6000023d1280>{number = 4, name = (null)}
2020-01-29 16:45:17.518188+0800 001---NSOperation初体验[2360:107174] 6-<NSThread: 0x6000023fc380>{number = 6, name = (null)}
2020-01-29 16:45:19.521176+0800 001---NSOperation初体验[2360:107172] 8-<NSThread: 0x6000023c4980>{number = 5, name = (null)}
2020-01-29 16:45:19.521176+0800 001---NSOperation初体验[2360:107174] 9-<NSThread: 0x6000023fc380>{number = 6, name = (null)}
复制代码

任务相互之间的依赖关系

- (void)demo{
    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"请求token");
    }];
    
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿着token,请求数据1");
    }];
    
    NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿着数据1,请求数据2");
    }];
    
    //因为异步,不好控制,我们借助依赖
    [bo2 addDependency:bo1];
    [bo3 addDependency:bo2];
    //注意这里一定不要构成循环依赖 : 不会报错,但是所有操作不会执行
    //[bo1 addDependency:bo3];
    //waitUntilFinished 堵塞线程
    [self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:NO];
    
    NSLog(@"执行完了?我要干其他事");
}
复制代码

打印:

2020-01-29 16:47:41.371621+0800 002---NSOperation深入浅出[2422:109106] 执行完了?我要干其他事
2020-01-29 16:47:41.872960+0800 002---NSOperation深入浅出[2422:109157] 请求token
2020-01-29 16:47:42.374790+0800 002---NSOperation深入浅出[2422:109156] 拿着token,请求数据1
2020-01-29 16:47:42.877750+0800 002---NSOperation深入浅出[2422:109156] 拿着数据1,请求数据2
复制代码

NSOperationQueue的挂起,继续,取消

/**
 关于operationQueue的挂起,继续,取消
 */
- (void)demo1{
    self.queue.name = @"Cooci";
    self.queue.maxConcurrentOperationCount = 2;
    for (int i=0; i< 20; i++) {
        [self.queue addOperationWithBlock:^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"%@-----%d",[NSThread currentThread],i);
        }];
    }
    
}

- (IBAction)pauseOrContinue:(id)sender {
    if (self.queue.operationCount == 0) {
        NSLog(@"当前没有操作,没有必要挂起和继续");
        return;
    }
    // 一个细节 正在执行的操作无法挂起
    if (self.queue.suspended) {
        NSLog(@"当前是挂起状态,准备继续");
    }else{
        NSLog(@"当前为执行状态,准备挂起");
    }
    self.queue.suspended = !self.queue.isSuspended;
    
}
- (IBAction)cancel:(id)sender {
    [self.queue cancelAllOperations];
}
复制代码

打印:

2020-01-29 16:52:44.854415+0800 002---NSOperation深入浅出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----1
2020-01-29 16:52:44.854415+0800 002---NSOperation深入浅出[2488:111998] <NSThread: 0x600001d1da40>{number = 4, name = (null)}-----0
2020-01-29 16:52:45.856950+0800 002---NSOperation深入浅出[2488:111999] <NSThread: 0x600001d1f080>{number = 5, name = (null)}-----2
2020-01-29 16:52:45.856988+0800 002---NSOperation深入浅出[2488:112000] <NSThread: 0x600001d04280>{number = 6, name = (null)}-----3
2020-01-29 16:52:46.277210+0800 002---NSOperation深入浅出[2488:111955] 当前为执行状态,准备挂起
2020-01-29 16:52:46.857767+0800 002---NSOperation深入浅出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----4
2020-01-29 16:52:46.857782+0800 002---NSOperation深入浅出[2488:111999] <NSThread: 0x600001d1f080>{number = 5, name = (null)}-----5
2020-01-29 16:52:48.967113+0800 002---NSOperation深入浅出[2488:111955] 当前是挂起状态,准备继续
2020-01-29 16:52:49.968072+0800 002---NSOperation深入浅出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----6
2020-01-29 16:52:49.968087+0800 002---NSOperation深入浅出[2488:111999] <NSThread: 0x600001d1f080>{number = 5, name = (null)}-----7
2020-01-29 16:52:50.969946+0800 002---NSOperation深入浅出[2488:112000] <NSThread: 0x600001d04280>{number = 6, name = (null)}-----8
2020-01-29 16:52:50.969946+0800 002---NSOperation深入浅出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----9
2020-01-29 16:52:51.465420+0800 002---NSOperation深入浅出[2488:111955] 全部取消
2020-01-29 16:52:51.973286+0800 002---NSOperation深入浅出[2488:111999] <NSThread: 0x600001d1f080>{number = 5, name = (null)}-----10
2020-01-29 16:52:51.973297+0800 002---NSOperation深入浅出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----11

复制代码

参考文章

iOS 多线程:『NSOperation、NSOperationQueue』详尽总结




原文地址:访问原文地址
快照地址: 访问文章快照