NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。
(一)使用步骤
- 创建操作:先将需要执行的操作封装到一个
NSOperation
对象中 - 创建队列:创建
NSOperationQueue
对象 - 将操作加入到队列中:将
NSOperation
对象添加到NSOperationQueue
对象中
之后,系统会自动将NSOperationQueue
中的NSOperation
取出来,在新线程中执行操作。
(二)基本使用
1. 创建操作
因为NSOperation是个抽象类,不能创建实例,所以我们通常使用它的子类来进行封装操作:
(1)使用子类NSInvocationOperation
// 1.创建 NSInvocationOperation 对象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testOp) object:nil];
// 2.调用 start 方法开始执行操作
[op start];
- (void)testOp {
NSLog(@"testOp--%@", [NSThread currentThread]);
}
输出:
testOp--<_NSMainThread: 0x600003bb82c0>{number = 1, name = main}
- 因为没有使用
NSOperationQueue
,并且我们是在当前线程(主线程)中执行一个操作,所以它是在当前线程(主线程)中完成操作的,并没有开启新线程。 - 如果在其他线程中执行操作,则打印的结果为其他线程。总之,它是在哪个线程创建并启动的,它就会在哪个线程执行。
(2)使用子类NSBlockOperation
// 1.创建 NSBlockOperation 对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOp---%@", [NSThread currentThread]);
}];
// 2.调用 start 方法开始执行操作
[op start];
输出:
blockOp---<_NSMainThread: 0x60000320c240>{number = 1, name = main}
这个其实和上边NSInvocationOperation
一样,只是使用block之后更加简洁了,也没有开启新线程,哪个线程创建并执行就是那个线程。
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];
输出:
op---<_NSMainThread: 0x600003fd0000>{number = 1, name = main}
3--Block task, <NSThread: 0x600003fd0200>{number = 6, name = (null)}
2--Block task, <NSThread: 0x600003fcb080>{number = 7, name = (null)}
4--Block task, <NSThread: 0x600003fcbb40>{number = 5, name = (null)}
1--Block task, <NSThread: 0x600003f890c0>{number = 3, name = (null)}
5--Block task, <NSThread: 0x600003f942c0>{number = 4, name = (null)}
使用子类NSBlockOperation
,并调用addexecutionBlock:
的情况下,blockOperationWithBlock:
方法中的操作和额外加的操作是在不同线程中异步执行的。同时,额外操作多的时候,blockOperationWithBlock:
方法中的操作有可能不会在当前线程中执行。
开启的线程数是由系统来决定的。
注意:addExecutionBlock:
方法必须在start()
方法之前执行,否则就会报错。
(3)使用继承自NSOperation自定义子类
我们可以通过自定义继承自NSOperation
的子类,重写main
或者start
来定义自己的NSOperation
对象。
如果只是重写了main
方法,有底层控制变更任务执行、完成状态以及任务退出。
如果重写了start
方法,需要自己控制任务状态。
重写main
方法比较简单,我们不需要管理线程的状态属性executing(是否正在执行)和finished(是否完成)。当main
执行完返回的时候,这个操作就结束了。
重写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);
输出:
0
test---<_NSMainThread: 0x600003508480>{number = 1, name = main}
test---<_NSMainThread: 0x600003508480>{number = 1, name = main}
1
重写start方法:
// .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);
输出:
0
test---<_NSMainThread: 0x60000386c000>{number = 1, name = main}
test---<_NSMainThread: 0x60000386c000>{number = 1, name = main}
0
2. 创建队列
NSOperationQueue
共有两种队列:主队列、自定义队列,其中自定义队列同时包含了串行、并发功能。
- 主队列:凡是添加到主队列中的操作,都会放到主线程中执行。
// 主队列获取方法 NSOperationQueue *queue = [NSOperationQueue mainQueue];
- 自定义队列(非主队列):添加到这种队列中的操作,会自动放到子线程中执行,同时包含了 串行、并发 功能。
// 自定义队列创建方法 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
3. 将操作加入队列中
有两种方法:
(1)- (void)addOperation:(NSOperation *)op;
需要先创建操作,再将创建好的操作加入到创建好的队列中去。
/**
* 使用 addOperation: 将操作加入到操作队列中
*/
- (void)addOperationToQueue {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建操作
// 使用 NSInvocationOperation 创建操作1
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
// 使用 NSInvocationOperation 创建操作2
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];
// 使用 NSBlockOperation 创建操作3
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op3 addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
}
}];
// 3.使用 addOperation: 添加所有操作到队列中
[queue addOperation:op1]; // [op1 start]
[queue addOperation:op2]; // [op2 start]
[queue addOperation:op3]; // [op3 start]
}
- (void)task1 {
for (int i = 0; i < 100; i++);
NSLog(@"1---%@", [NSThread currentThread]);
}
- (void)task2 {
for (int i = 0; i < 100; i++);
NSLog(@"2---%@", [NSThread currentThread]);
}
输出:
1---<NSThread: 0x6000002fa700>{number = 7, name = (null)}
2---<NSThread: 0x6000002a0480>{number = 4, name = (null)}
4---<NSThread: 0x6000002bd400>{number = 8, name = (null)}
3---<NSThread: 0x6000002e65c0>{number = 5, name = (null)}
3---<NSThread: 0x6000002e65c0>{number = 5, name = (null)}
4---<NSThread: 0x6000002bd400>{number = 8, name = (null)}
添加到队列中的操作开启了新线程来完成,进行并发执行。
(2)- (void)addOperationWithBlock:(void (^)(void))block;
无需先创建操作,在 block
中添加操作,直接将包含操作的 block
加入到队列中。
/**
* 使用 addOperationWithBlock: 将操作加入到操作队列中
*/
- (void)addOperationWithBlockToQueue {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.使用 addOperationWithBlock: 添加操作到队列中
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
}
输出:
3---<NSThread: 0x6000007c8e80>{number = 8, name = (null)}
1---<NSThread: 0x6000007901c0>{number = 5, name = (null)}
2---<NSThread: 0x600000790b80>{number = 6, name = (null)}
2---<NSThread: 0x600000790b80>{number = 6, name = (null)}
1---<NSThread: 0x6000007901c0>{number = 5, name = (null)}
3---<NSThread: 0x6000007c8e80>{number = 8, name = (null)}
可以看出,使用 addOperationWithBlock:
将操作加入到操作队列后能够开启新线程,进行并发执行。
(三)NSOperationQueue控制串行执行、并行执行
操作队列有一个属性,最大并发操作数,用来控制一个特定的队列中可以有多少个操作同时并发执行,也就是一个队列中同时能并发执行的最大操作数。
@property NSInteger maxConcurrentOperationCount;
注意:这里
maxConcurrentOperationCount
控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而且一个操作也并非只能在一个线程中运行。
maxConcurrentOperationCount
默认为-1,表示不进行限制,可进行并发执行maxConcurrentOperationCount
为1时,此时队列为串行队列,只能串行执行maxConcurrentOperationCount
大于1时,队列为并发队列。操作并发执行。当这个值超过了系统限制,就会自动调整为系统设定的值。
(四)NSOperation操作依赖
NSOperation
、NSOperationQueue
最吸引人的就是它能够添加操作之间的依赖关系,通过依赖关系,我们就可以很方便的控制操作之间的执行先后顺序。
NSOperation
提供了3个接口供我们使用依赖:
- (void)addDependency:(NSOperation *)op
添加依赖,是当前操作依赖op的完成,op完成之后才会执行当前操作。- (void)removeDependency:(NSOperation *)op
移除依赖,取消当前操作对操作op的依赖。@property (readonly, copy) NSArray<NSOperation *> *dependencies;
操作对象的一个属性,在当前操作开始执行之前完成执行的所有操作对象数组。
1. 没有添加依赖时
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];
输出:
firstOperation
thirdOperation
secondOperation
没有添加依赖,执行都是并发执行的,没有次序可言。
2. 添加依赖后
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];
输出:
firstOperation
secondOperation
thirdOperation
在我们添加依赖之后,操作都是按照添加依赖的次序来的。
3. 相互依赖
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];
输出:
thirdOperation
在添加相互依赖之后,这两个都不执行了。
(五)NSOperation优先级
依赖只是一种执行关系罢了,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。
其中只有op1、op4没有需要依赖的操作,所以op1、op4就是处于准备就绪状态的操作。
op2、op3都有依赖的操作,所以op2、op3都不是准备就绪的操作。
当op4完成时,op3进入就绪状态;当op3完成时,op2进入就绪状态。
queuePriority
queuePriority
属性决定了已进入就绪状态下的操作之间的开始执行顺序。优先级不能取代依赖关系,该属性仅决定开始执行顺序,并不能保证完成执行顺序。- 如果一个队列中既包含高优先级操作、也有低优先级操作,并且这两个操作都已经准备就绪,那么队列就会先执行高优先级操作。
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];
输出:
begin firstOperation
begin thirdOperation
begin secondOperation
firstOperation end
thirdOperation end
secondOperation end
- 如果我们将最大操作执行数设置为1,那么队列中操作数将会按添加的顺序串行执行,一个操作执行完才会执行另一个操作。
- 如果队列中所有的操作的优先级相同,并且也进入就绪状态,那么执行的顺序就按照提交到队列的顺序执行。否则,队列总是执行相对于其他就绪操作优先级更高的操作。
问题:为什么我们设置了优先级,他还是不会按照优先级大小来执行操作?
可以通过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;
设置优先级和服务质量都不能保证代码执行顺序的绝对性,只是改变其先执行或后执行的概率。
如果我们要确保操作的执行的先后顺序,即操作间有同步关系,我们应该使用依赖关系来确保绝对的执行顺序,即使操作对象位于不同的操作队列中。在操作对象的所有依赖操作完成执行之前,操作对象不会被视为已准备好执行。
(六)NSOperation、NSOperationQueue线程间的通信
在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]);
}];
}];
原文地址:https://blog.csdn.net/weixin_52192405/article/details/126215451
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_6013.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!