文章目录
- Grand Central Dispatch(GCD)
- NSThread
- NSOperation和NSOperationQueue
Grand Central Dispatch(GCD)
1. 什么是GCD
异步执行任务的技术之一,开发者只需定义想执行的任务,并追加到适当的Dispatch Queue中,GCD就成生成必要的线程并计划执行任务。且该方法比单纯使用线程更有效率(gcd的线程管理是系统实现,统一管理)
2.GCD的API
苹果官方对GCD的说明:开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中
2.1Dispatch Queue是什么?
执行处理的等待队列,开发者通过GCD的API将任务(通常是block代码块)添加到Dispatch Queue。 Dispatch Queue按照追加顺序(先进先出),执行处理。
在GCD中有两种队列,分别是
-
串行队列(Serial Dispatch Queue)
-
并发队列(Concurrent Dispatch Queue)
同时我们需要提前了解,async是异步执行,sync是同步执行
同步执行sync | 异步执行async | |
---|---|---|
Serial串行队列 | 当前线程,顺序执行 | 其它线程,顺序执行 |
Concurrent并行队列 | 当前线程,顺序执行 | 很多线程,并行执行 |
2.2如何获取Dispatch Queue
2.2.1通过GCD的API获取 dispatch_queue_create
//第一个参数表示队列的唯一标识符,用于 DEBUG,可为空。队列的名称推荐使用应用程序 ID 这种逆序全程域名。
//第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。 为NULL生成serial
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.serialDispatchQueue", NULL);
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.example.gcd.concurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
虽然说Serial Dispatch Queue 串行处理队列,同时只能执行一个追加处理,但是可以生成多个队列进行同时处理
需要注意的是,每生成一个Serial Dispatch Queue,系统就会开一个线程对应处理
另外多线程也会带来线程安全,数据竞争的问题
想要并行执行且不发生数据竞争的问题就需要 Concurrent Dispatch Queue,通常使用dispatch_queue_create生成的Dispatch Queue在使用结束后通过
dispatch_release(mySerialDispatchQueue);
//同时也有retain函数
dispatch_retain(mySerialDispatchQueue);
2.2.2Main Dispatch Queue/Global Dispatch Queue
第二种方法是获取系统提供的标准的Dispatch Queue。系统提供的DispatchQueue。 mainDispatchQueue是主线程(只有一个)执行的Queue,所以自然是Serial Dispatch Queue
Global Dispatch Queue是所有程序都能使用的Concurrent Dispatch Queue
有四个优先级
Main Dispatch Queue/Global Dispatch Queue的获取方法:
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
高优先级:DISPATCH_QUEUE_PRIORITY_HIGH
默认优先级:DISPATCH_QUEUE_PRIORITY_DEFAULT
低优先级:DISPATCH_QUEUE_PRIORITY_LOW
后台优先级:DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
对于main dispatch queue 和 global dispatch queue执行dispatch_retain和dispatch_release函数不会引起任何变化,也不会存在任何问题。
这也是使用标准 Dispatch Queue的好处
//默认优先级Global Dispatch Queue执行block
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//并行执行的处理
dispatch_async(dispatch_get_main_queue(), ^{
//只能主线程中执行的处理
});
});
2.3dispatch_set_target_queue
dispatch_set_target_queue用来变更Dispatch Queue执行的优先级
dispatch_queue_t muSerialDispatchQueue = dispatch_queue_create("com.example.gcd.serialDispatchQueue", NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//第一个参数:指定要变更优先级的Dispatch Queue
//第二个参数:指定一个队列,使用与其相同执行优先级
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);
//可将多个并行的Serial Dispatch Queue用dispatch_set_target_queue指定其中某一个Serial Dispatch Queue,防止处理并行执行
2.4dispatch_after
在指定的时间后,将任务追加到Dispatch Queue中。
//从现在开始到3秒后
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
//第一个参数:指定时间,dispatch_time_t类型,用dispatch_time或者dispatch_walltime函数生成
//第二个参数:指定要追加处理的Dispatch Queue
//第三个参数:指定要执行的Block
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"waited at least tree seconds.");
});
上文代码在3秒后用dispatch 将追加Block到Main Dispatch Queue当中,Main Dispatch Queue在主线程的RunLoop中执行,假设每隔1/60秒可以轮询一次,那么Block最快3秒后执行,最慢时3秒+1/60秒后执行。
2.5Dispatch Group
Dispatch Queue中的处理全部结束,想执行结束处理,如果只使用一个Serial Dispatch Queue时,只要将向执行的处理全部追加到该Serial Dispatch Queue中并在最后追加结束处理,即可实现。 但是使用Concurrent Dispatch Queue时,同时使用多个Dispatch Queue,将会变得灰常发咋。
这种情况需要使用Dispatch Group。例如:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create(); //由create可知,还具有release,retain方法
//dispatch_group_async与dispatch_async相同,都是追加处理到Dispatch Queue当中,不过会指定一个参数 group,指定的Block属于指定的Dispatch Group
dispatch_group_async(group, queue, ^{NSLog(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"done");
});
dispatch_release(group);
//输出如下:
blk1
blk2
blk0
done
blk的输出顺序并不确定,但是done一定是最后输出。 无论向什么样的Dispatch Queue中追加处理,使用Dispatch Group都可见识这些处理执行的结束。一旦检测到结束,就可讲结束的处理追加到Dispatch Queue当中。
在追加到Dispatch Group中处理全部直接结束后,会执行dispatch_group_notify,将会把指定的Block追加到Dispatch Queue当中。
在Dispatch Group中也可以使用dispatch_group_wait函数仅等待全部处理执行结束。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{NSLog(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER); //永远等待,直到处理全部结束
dispatch_release(group);
//如果等待一秒 那么如下处理
dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC)
long result = dispatch_group_wait(group, time);
//这一行已经过了指定时间,返回0表示全部处理结束,非0未结束
if (result == 0) {
//属于Dispatch Group的全部处理执行结束
} else {
//属于Dispatch Group的某一个处理还在执行中
}
或者使用队列组的 dispatch_group_enter
、dispatch_group_leave
组合来实现 dispatch_group_async
dispatch_group_enter
标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
dispatch_group_leave
标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait
解除阻塞,以及执行追加到 dispatch_group_notify
中的任务。
/**
* 队列组 dispatch_group_enter、dispatch_group_leave
*/
- (void)groupEnterAndLeave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程.
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---end");
});
}
2.6dispatch_barrier_async
通常我们读写处理的方法是: 将读取处理追加到Concurrent Dispatch Queue当中,写入处理(任一个读取处理没有执行的状态下)追加到Serial Dispatch Queue(写入处理结束前,读取处理不可执行)
行之有效的方法是使用dispatch_barrier_async
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.serialDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, blk0_for_reading);
dispatch_async(queue, blk1_for_reading);
dispatch_async(queue, blk2_for_reading);
/*
* 如在这里执行写入处理,写入的内容被之后的任务读取
* dispatch_async(queue, blk3_for_writing); 将发生不可预期的错误
* 需要使用dispatch_barrier_async
* 会等待这里之上的所有并行结束后,将指定的处理追加到Concurrent Dispatch Queue 当中,然后由dispatch_barrier_async追加的处理执行完毕后,Concurrent Dispatch Queue 才恢复并行动作
*/
dispatch_async(queue, blk3_for_reading);
dispatch_async(queue, blk4_for_reading);
dispatch_release(queue);
注意:这里阻塞的是Concurrent队列,而不是线程
dispatch_barrier_sync会阻塞队列,并且阻塞线程
2.7dispatch_sync
- dispatch_async 异步追加任务,该函数不做任何等待
- dispatch_sync 同步追加任务,再将Block追加结束前,该函数会一直等待
- dispatch_sync 使用不恰当使用会导致死锁
//主队列死锁
(void) syncMain() {
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{NSLog(@"hello");});
//下面处理也一样
dispatch_async(queue, ^{
dispatch_sync(queue, ^{NSLog(@"hello");});
});
}
主线程执行会产生死锁
分析:在主线程执行syncMain方法,相当于把syncMain任务放到了主线程队列中,同步执行会等待当前任务执行完毕才会接着执行,然后当我们追加了任务到主队列中,新任务就等主线程处理syncMain任务,而syncMain任务需要等新任务执行完毕才能继续执行,互相等待,产生死锁。
当代码执行到dispatch_sync时候,会立马阻塞主线程,然后把Block的任务放到main_queue中,main_queue的任务会被取出来放到主线程中执行,可是主线程被阻塞,所以Block任务不能完成,它不完成,dispatch_sync就一直阻塞主线程。
//嵌套队列, 串行队列+异步执行 也会产生死锁
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
NSLog("开始 - %@", NSThread.currentThread())
dispatch_async(queue, ^{ // 异步执行 + 串行队列
NSLog("sync之前 - %@", NSThread.currentThread())
dispatch_sync(queue, ^{ // 同步执行 + 当前串行队列
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog("sync中 - %@", NSThread.currentThread())
});
NSLog("sync之后 - %@", NSThread.currentThread())
});
NSLog("结束 - %@", NSThread.currentThread())
分析:可见没有执行async中的 “sync中” 和“sync后”两句。 而我们知道async异步添加任务,会开辟一个新的线程,此时有两个线程, 一个是主线程 执行了“开始 -” “结束 -” 而在async中开辟的新线程先执行 “sync之前”, 紧接着执行 dispatch_sync:
高潮来了:“dispatch_sync”同步执行,那么会先阻塞自己所在线程去将block放在queue当中,然后去执行queue中添加的任务, 但是由于我们之前执行了async给串行队列中已经添加一条任务是去执行dispatch_sync, 两个任务相互等待阻塞,导致死锁。
为何同步执行串行的队列不会死锁?
- (void)syncSerial {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"syncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"syncSerial---end");
}
首先我们要清楚,主线程一直拥有一个主队列,执行同步的串行队列时, 先执行syncSerial,会将当前任务放在主队列当中,然后sync中的block每次会被依次添加到创建的自定义的Serial队列中,并在加入的时候立即执行,直到执行完毕,自定义队列任务就完成了,执行完毕后会告知主队列syncSerial执行完成,主队列任务完成。
可看出主队列就一个任务syncSerial,其它任务都在串行队列中
dispatch_barrier_async作用:
-
等待追加的处理全部执行结束后
- 再追加处理到Dispatch Queue中
2.8dispatch_apply
dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API,将函数按指定的次数将指定的Block追加到Dispatch Queue中, 并等待全部处理执行结束
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//参数1: 重复次数
//参数2: 追加任务的队列
//参数3: 带参数的Block,区分各个Block
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu", index);
});
NSLog(@"done");
dispatch_apply函数与dispatch_sync函数相同,会等待处理执行结束,推荐在dispatch_async函数中非同步的执行dispatch_apply函数
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//在global queue中非同步执行
dispatch_async(queue, ^{
//global queue等待dispatch_apply函数全部处理执行结束
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu", index);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"done");
});
});
2.9dispatch_suspend/dispatch_resume 挂起/恢复
-
dispatch_suspend(queue); 挂起指定的Dispatch Queue
-
dispatch_resume(queue); 恢复指定的Dispatch Queue
相当于暂停播放按钮
2.10 Dispatch Semaphore gcd中信号量
Dispatch Semaphore持有计数的信号,该计数是多线程编程中的计数类型信号。
信号量为0阻塞,大于0不阻塞,通过改变信号量的值来同步线程
初始为1的信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1;)
函数等待Dispatch Semaphore的计数值大于或者等于1,满足条件时,对该计数做减法并返回
dispatch_semphore_wait(semphore, DISPATCH_TIME_FOREVER);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_semaphore_wait(semaphore, time);
if (result == 0) {
//这里计数值减去1
//做相应处理
} else {
//不满足条件,一直等待
}
//计数值加1的操作
dispatch_semaphore_signal(semaphore);
//线程同步
- (void)semaphoreSync {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"semaphore---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int number = 0;
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
number = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %zd",number);
}
2.11 dispatch_once
dispatch_once函数保证程序执行中只执行一次指定处理的AIPI。 例如初始化
static int initialized = NO;
if (initialized == NO) {
initialized = YES;
}
使用dispatch_once函数,源码写为:
static dispatch_once_t pred;
dispatch_once(&pred, ^{
//初始化
})
通过dispatch_once函数,初始化在多线程环境下执行,可保证百分之百的安全
参考文献
https://www.jianshu.com/p/2d57c72016c6
《Objective-C高级编程 iOS与OS X多线程和内存管理》
https://www.jianshu.com/p/0b0d9b1f1f19
NSThread
创建线程
//创建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
// 启动
[thread start];
//创建并启动
[self performSelectorInBackground:@selector(run:) withObject:nil];
/***************一些其它方法****************/
//取消线程
- (void)cancel;
//启动线程
- (void)start;
//判断某个线程的状态的属性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//设置和获取线程名字
-(void)setName:(NSString *)n;
-(NSString *)name;
//获取当前线程信息
+ (NSThread *)currentThread;
//获取主线程信息
+ (NSThread *)mainThread;
//使当前线程暂停一段时间,或者暂停到某个时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
NSOperation和NSOperationQueue
NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue
分别对应 GCD 的 任务(操作)和队列(操作队列)
- 操作(Operation):
- 执行操作的意思,换句话说就是你在线程中执行的那段代码。
- 在 GCD 中是放在 block 中的。在 NSOperation 中,我们使用 NSOperation 子类 NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。
- 操作队列(Operation Queues):
- 这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
- 操作队列通过设置**最大并发操作数(maxConcurrentOperationCount)**来控制并发、串行。
- NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。
操作步骤:
- 将要执行的任务封装到一个
NSOperation
对象中。 - 将此任务添加到一个
NSOperationQueue
对象中。
NSOperation
只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation
和 NSBlockOperation
。创建一个 Operation 后,需要调用 start
方法来启动任务,它会 默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel
方法即可。
- NSInvocationOperation : 需要传入一个方法名。
1.创建操作
1.1添加任务:NSInvocationOperation
//1.创建NSInvocationOperation对象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//2.开始执行
[operation start];
- (void)run {
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}
1.2添加任务:NSBlockOperation
默认会在当前线程执行,但是通过方法addExecutionBlock(可选)添加执行多个Block,Operation任务会多线程并发执行。
//1.创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//添加多个Block
for (NSInteger i = 0; i < 5; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
}];
}
//2.开始任务
[operation start];
NOTE:addExecutionBlock
必须 在start之前执行,否则会报错
1.3自定义Operation实现
可以通过重写 main
或者 start
方法 来定义自己的 NSOperation 对象。重写main
方法比较简单,我们不需要管理操作的状态属性 isExecuting
和 isFinished
。当 main
执行完返回的时候,这个操作就结束了。
先定义一个继承自 NSOperation 的子类,重写main
方法。
// YSCOperation.h 文件
#import <Foundation/Foundation.h>
@interface YSCOperation : NSOperation
@end
// YSCOperation.m 文件
#import "YSCOperation.h"
@implementation YSCOperation
- (void)main {
if (!self.isCancelled) {
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"1---%@", [NSThread currentThread]);
}
}
}
@end
使用时候倒入头文件
/**
* 使用自定义继承自 NSOperation 的子类
*/
- (void)useCustomOperation {
// 1.创建 YSCOperation 对象
YSCOperation *op = [[YSCOperation alloc] init];
// 2.调用 start 方法开始执行操作
[op start];
}
没有使用NSOperationQueue、在主线程单独使用自定义继承自 NSOperation 的子类的情况下,是在主线程执行操作,并没有开启新线程。
2.创建队列
- 主队列
- 凡是添加到主队列中的操作,都会放到主线程中执行。
- 定义队列(非主队列)
- 添加到这种队列中的操作,就会自动放到子线程中执行。
- 同时包含了:串行、并发功能。
//主队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
//自定义队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
2.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)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.NSOperationQueue 控制串行执行、并发执行
这里有个关键属性 maxConcurrentOperationCount
,叫做最大并发操作数。用来控制一个特定队列中可以有多少个操作同时参与并发执行。
注意:这里
maxConcurrentOperationCount
控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而且一个操作也并非只能在一个线程中运行。
最大并发操作数:maxConcurrentOperationCount
maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。
/**
* 设置 MaxConcurrentOperationCount(最大并发操作数)
*/
- (void)setMaxConcurrentOperationCount {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.设置最大并发操作数
queue.maxConcurrentOperationCount = 1; // 串行队列
// queue.maxConcurrentOperationCount = 2; // 并发队列
// queue.maxConcurrentOperationCount = 8; // 并发队列
// 3.添加操作
[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]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
}
}];
}
4. NSOperation 操作依赖
通俗来讲,如果是B依赖A,那么会先执行A,完成后再执行B。
NSOperation为我们提供了三个接口
- (void)addDependency:(NSOperation *)op;
添加依赖,使当前操作依赖于操作 op 的完成。- (void)removeDependency:(NSOperation *)op;
移除依赖,取消当前操作对操作 op 的依赖。@property (readonly, copy) NSArray<NSOperation *> *dependencies;
在当前操作开始执行之前完成执行的所有操作对象数组。
参考文献:
https://bujige.net/blog/iOS-Complete-learning-NSOperation.html