IOS之多线程基础(OC)
- 基本概念理解
- 并行和并发
- 并发和并行是即相似又有区别(微观概念)
- 进程和线程
- 进程
- 线程
- 进程和线程的区别
- 线程调度
- 多线程
- NSThread
- 类方法创建NSThread
- 实例方法创建NSThread
- 实例
- @synchornized(object){}
- NSLOCK
- GCD
- 死锁
- GCD的执行顺序
- 串行队列先异步后同步:
- performSelector
- 队列和任务执行
- 同步执行串行队列任务
- 异步执行串行队列任务
- 同步执行并行队列任务
- 异步执行并行队列任务
- 全局队列 和主队列
- 全局队列
- 主队列
- GCD常用函数
- GCD中Group的用法
- Dispatch Semaphore(队列中的信号值)
- 保持线程同步
- 保证线程安全,为线程加锁:
- dispatch_once_t
- dispatch_after
- GCD定时器
- dispatch_barrier
- NSOperation
- NSBlockOperation
- NSInvocationOperation
- NSOperationQueue
基本概念理解
并行和并发
并发和并行是即相似又有区别(微观概念)
- 并行:指两个或者多个事件在同一时刻点发生
- 并发:指两个或则多个事件在同一时间段内发生
- 在操作系统中,在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单CPU系统中,每一时刻却仅能有一道程序执行(时间片),故微观上这些程序只能是分时地交替执行。倘若计算机系统中有多个CPU,则这些可以并发执行的程序便可被分配到多个处理器上,实现多任务并行执行,即利用每个处理器来处理一个可并发执行的程序,这样,多个程序便可以同时执行,因为是微观的,所以大家在使用电脑的时候感觉就是多个程序是同时执行的。
- 线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度时间片即CPU分配给各个程序的运行时间(很小的概念).
进程和线程
进程
- 进程: 计算机系统中运行的一个应用程序,每个进程是独立的。
- 多线程:操作系统中同时运行多个程序
线程
- 线程:一个进程想要执行任务,必须有一个线程,而且每一个进程中必有至少有一个线程,进程中的所有任务都在线程中执行
- 多线程:在同一个进程中同时运行多个任务
- 线程的执行顺序:一个线程中的任务都是串行执行的。一个线程中有多个任务时,只能按照顺序一个一个来执行,同一个时刻,一个线程只能执行一个任务
进程和线程的区别
- 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间) 是独立的,至少有一个线程
- 线程:堆空间是共享的,占空间是独立的,线程的消耗资源也比进程小,相互之间可以影响,又称为轻型进程或进程元。
因为一个进程中的多个线程是并发运行的,那么从微观角度上考虑也是有先后顺序的,那么哪个线程执行完全取决于CPU调度器,程序员是控制不了的。
我们可以把多线程并发性看作是多个线程在瞬间抢CPU资源,谁抢到资源谁就运行,这也造就了多线程的随机性
线程调度
- 计算机通常只有一个CPU时,在任意时刻只能执行一条计算机指令,每一个进程只有获得CPU的使用权才能执行指令.
所谓多进程并发运行,从宏观上看,其实是各个进程轮流获得CPU的使用权,分别执行各自的任务.
多线程
- IOS开发多线程的3种常用方式
NSThread
- NSThread有两种创建方法,一种是类方法,一种是实例方法。类方法创建的时候需要我们确定好执行的任务,没有任何返回
类方法创建NSThread
/// 使用类方法来创建NSThread
- (void)creatThread {
//1. block方式
if (@available(iOS 10.0, *)) {
//这个类创建线程方法 IOS版本 >= 10.0
[NSThread detachNewThreadWithBlock:^{
NSLog(@"detachNewThreadWithBlock ==current thread =====%@",[NSThread currentThread]);
}];
} else {
// Fallback on earlier versions
//SEL创建方式
[NSThread detachNewThreadSelector:@selector(runThread) toTarget:self withObject:nil];
}
}
- (void)runThread{
NSLog(@"detachNewThreadSelector ===current thread =====%@",[NSThread currentThread]);
}
打印结果:
打印当前线程,输出结果是新开一个线程
实例方法创建NSThread
- (void)creatThread2 {
//如果是实例方法创建的NSThread对象,我们需要手动调用方法[thread start]来启动线程
if (@available(iOS 10.0, *)) {
//blcok创建方式
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"initWithBlock == current thread====%@",[NSThread currentThread]);
}];
[thread start];
}else {
// Fallback on earlier versions
//SEL创建方式
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(runThread) object:nil];
[thread start];
}
}
打印结果:
- NSThread除了创建的两种方式,还有许多其他的方法 例如:
[thread setName:@"设置线程的名称"];
//设置线程的优先级,由0 到 1 的浮点数来制定,其中1.0是最高优先级。
[thread setThreadPriority:1];
//推出当前线程
[NSThread exit];
//使当前线程睡眠-> 单位是秒
[NSThread sleepForTimeInterval:1];
//使当前线程沉睡直到某个时间
[NSThread sleepUntilDate:[[NSDate alloc] init]];
//判断是否在主线程
[NSThread isMainThread]
实例
接下来我们使用经典的买票问题来模拟并解决NSthread中的线程同步问题,有两个售票员开始卖票,一共20张票,模拟该场景:
- (void)exampleThread {
//1.首先我们设置总票数
self.sumCount = 10;
//2。创建两个售票员在卖票
__weak typeof(self) weakSelf = self;
if (@available(iOS 10.0, *)) {
[NSThread detachNewThreadWithBlock:^{
while (weakSelf.sumCount > 0) {
[NSThread sleepForTimeInterval:1];
weakSelf.sumCount--;
NSLog(@"还有%d张票",weakSelf.sumCount);
}
}];
[NSThread detachNewThreadWithBlock:^{
while (weakSelf.sumCount > 0) {
[NSThread sleepForTimeInterval:1];
weakSelf.sumCount --;
NSLog(@"还有%d张票",weakSelf.sumCount);
}
}];
} else {
// Fallback on earlier versions
}
}
打印结果:
结果出现了 相同的票数,很明显这个结果是有问题的,这 是因为同一张票是被卖出了两次。为了避免这种情况,通常的做法就是 给线程上锁,当某一条线程对数据操作时,先给数据上锁,别的线程阻塞,等到这条线程操作结束,在开锁,别的线程在进去,上锁,操作,开锁…这样就保证了数据的操作。
@synchornized(object){}
我们可以使用@synchornized(object){}来上锁,括号里面可以填写任意对象,但要注意的是,必须填写线程共有的变量才能实现上锁,局部变量是无效的,原因是如果使用局部变量,就会创建多个锁,这些锁之间并无关联,所以与不上锁没有区别
- (void)exampleThread {
//1.首先我们设置总票数
self.sumCount = 10;
//2。创建两个售票员在卖票
__weak typeof(self) weakSelf = self;
if (@available(iOS 10.0, *)) {
[NSThread detachNewThreadWithBlock:^{
//上锁
@synchronized (weakSelf) {
while (weakSelf.sumCount > 0) {
[NSThread sleepForTimeInterval:1];
weakSelf.sumCount--;
NSLog(@"还有%d张票",weakSelf.sumCount);
}
}
}];
[NSThread detachNewThreadWithBlock:^{
//上锁
@synchronized (weakSelf) {
while (weakSelf.sumCount > 0) {
[NSThread sleepForTimeInterval:1];
weakSelf.sumCount --;
NSLog(@"还有%d张票",weakSelf.sumCount);
}
}
}];
} else {
// Fallback on earlier versions
}
}
打印结果:
因为我们上了锁,在锁内进行数据操作时,其他线程都会阻塞在外面,这个时候其实线程并不是并发执行的,所以我们不难想到,所内执行的任务越少,那么这段代码执行的效率就越高。在此基础上,我们可以对前面的枷锁进行一个小小的修改
- (void)exampleThread {
//1.首先我们设置总票数
self.sumCount = 10;
//2。创建两个售票员在卖票
__weak typeof(self) weakSelf = self;
if (@available(iOS 10.0, *)) {
[NSThread detachNewThreadWithBlock:^{
// @synchronized (weakSelf) {
// while (weakSelf.sumCount > 0) {
// [NSThread sleepForTimeInterval:1];
// weakSelf.sumCount--;
// NSLog(@"还有%d张票",weakSelf.sumCount);
// }
// }
while (true) {
[NSThread sleepForTimeInterval:1];
@synchronized (self) {
if (weakSelf.sumCount < 1) {
break;
}
self.sumCount --;
NSLog(@"还有%d张票",weakSelf.sumCount);
}
}
}];
[NSThread detachNewThreadWithBlock:^{
// @synchronized (weakSelf) {
// while (weakSelf.sumCount > 0) {
// [NSThread sleepForTimeInterval:1];
// weakSelf.sumCount --;
//
// NSLog(@"还有%d张票",weakSelf.sumCount);
// }
// }
while (true) {
[NSThread sleepForTimeInterval:1];
@synchronized (self) {
if (weakSelf.sumCount < 1) {
break;
}
self.sumCount --;
NSLog(@"还有%d张票",weakSelf.sumCount);
}
}
}];
} else {
// Fallback on earlier versions
}
}
打印结果:
通过比对两次的买票打印时间,我们发现 我们修改后的卖票效率几乎提升了一倍
NSLOCK
当然我们也可以用NSLock来进行上锁,使用NSLock需要创建一个NSLockd 的实例,然后调用lock() 和 unlock() 两个方法来进行就锁和解锁的操作
- (void)exampleThread2 {
//1.首先我们设置总票数
self.sumCount = 10;
self.lock = [[NSLock alloc] init];
//2。创建两个售票员在卖票
__weak typeof(self) weakSelf = self;
if (@available(iOS 10.0, *)) {
[NSThread detachNewThreadWithBlock:^{
while (true) {
[NSThread sleepForTimeInterval:1];
[weakSelf.lock lock];
if (weakSelf.sumCount < 1) {
break;
}
self.sumCount --;
NSLog(@"还有%d张票",weakSelf.sumCount);
[weakSelf.lock unlock];
}
}];
[NSThread detachNewThreadWithBlock:^{
while (true) {
[NSThread sleepForTimeInterval:1];
[weakSelf.lock lock];
if (weakSelf.sumCount < 1) {
break;
}
self.sumCount --;
NSLog(@"还有%d张票",weakSelf.sumCount);
[weakSelf.lock unlock];
}
}];
} else {
// Fallback on earlier versions
}
}
打印结果:
结果输出和上面使用@synchronized(object){} 加锁一样的效果。
不知道你还记得不记得atomic,这个就修饰了属性的原子性,如果直接把属性修饰改为atomic,会不会就不需要我们加锁了呢?我试过,不行!这是因为atomic只会对该属性的Getter和Setter方法上锁,而我们很显然是在别的方法里面对数据进行操作,所以并没什么卵用。同时也因为atomic太耗性能,所以在实际开发中,我们一般都不使用它来修饰变量
GCD
- GCD(Grand Central Dispatch): 翻译过来是宏伟的中枢调度,是一种基于C语言的并发编程技术。它是苹果为多核的并行运算提出的解决方案,会自动调度系统资源,所以效率很高。GCD并不直接操作线程,而是操作队列和任务,我们只需要把任务添加到队列里,然后指定任务的执行方式,GCD就会自动调度线程执行任务。
- GCD的队列都是以block的形式存在的,队列有两种: 串行队列/并行队列
死锁
死锁就是队列引起的循环等待
一个比较常见的死锁例子: 主队列同步
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"deallock");
});
// Do any additional setup after loading the view, typically from a nib.
}
在主线程中运用主队列同步,也就是把任务放到了主线程的队列中。
同步对于任务是立刻执行的,那么当把任务放进主队列时,它就会立马执行,只有执行完这个任务,viewDidLoad才会继续向下执行。
而viewDidLoad和任务都是在主队列上的,由于队列的先进先出原则,任务又需等待viewDidLoad执行完毕后才能继续执行,viewDidLoad和这个任务就形成了相互循环等待,就造成了死锁。
想避免这种死锁,可以将同步改成异步dispatch_async,或者将dispatch_get_main_queue换成其他串行或并行队列,都可以解决。
同样下面的代码也会造成死锁:
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue, ^{
NSLog(@"deadlock");
});
});
外面的函数无论是同步还是异步都会造成死锁。
这是因为里面的任务和外面的任务都在同一个serialQueue队列内,又是同步,这就和上边主队列同步的例子一样造成了死锁
解决方法也和上边一样,将里面的同步改成异步dispatch_async,或者将serialQueue换成其他串行或并行队列,都可以解决
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue2, ^{
NSLog(@"deadlock");
});
});
GCD的执行顺序
串行队列先异步后同步:
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
NSLog(@"2");
});
NSLog(@"3");
dispatch_sync(serialQueue, ^{
NSLog(@"4");
});
NSLog(@"5");
打印顺序是13245
原因是:
首先先打印1
接下来将任务2其添加至串行队列上,由于任务2是异步,不会阻塞线程,继续向下执行,打印3
然后是任务4,将任务4添加至串行队列上,因为任务4和任务2在同一串行队列,根据队列先进先出原则,任务4必须等任务2执行后才能执行,又因为任务4是同步任务,会阻塞线程,只有执行完任务4才能继续向下执行打印5
所以最终顺序就是13245。
这里的任务4在主线程中执行,而任务2在子线程中执行。
如果任务4是添加到另一个串行队列或者并行队列,则任务2和任务4无序执行(可以添加多个任务看效果)
performSelector
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(test:) withObject:nil afterDelay:0];
});
这里的test方法是不会去执行的,原因在于
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
这个方法要创建提交任务到runloop上的,而gcd底层创建的线程是默认没有开启对应runloop的,所有这个方法就会失效。
而如果将dispatch_get_global_queue改成主队列,由于主队列所在的主线程是默认开启了runloop的,就会去执行(将dispatch_async改成同步,因为同步是在当前线程执行,那么如果当前线程是主线程,test方法也是会去执行的)。
队列和任务执行
- 串行队列只能等一个任务完成之后,在执行下一个任务。
/**
@param 参数1 队列的名称
@param dispatch_queue_attr_t DISPATCH_QUEUE_SERIAL: 串行队列 DISPATCH_QUEUE_CONCURRENT:并发队列
*/
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
- 并发队列可以同时调度多个任务
/**
@param 参数1 队列的名称
@param dispatch_queue_attr_t DISPATCH_QUEUE_SERIAL: 串行队列 DISPATCH_QUEUE_CONCURRENT:并发队列
*/
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
- 同步执行会等待当前任务完成才会执行下一个任务,不会开启新的线程。
/**
同步执行队列
参数: 1队列, blcok(任务)
*/
dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
- 异步执行不会等待当前任务完成就会执行下一个任务,可以开启新的线程。(如果是在主队列,则不会开启新的线程,因为主队列都会在主线程执行)
/**
异步执行队列
参数: 1队列, blcok(任务)
*/
dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
队列和任务都有两种,排列组合以后就有四种情况,在不同的情况下,执行的结果可能会有差异,如果不清楚原理比较容易混淆。这里有一个简单的方法去分析执行情况:队列的类型决定了能不能同时执行多个任务(串行队列一次只能执行一个任务,并发队列一次可以执行多个任务),执行的方式决定了会不会开启新线程(同步执行不会开启新线程,异步执行可以开启新线程)。
接下来我们演示下4中情况
同步执行串行队列任务
//创建一个串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
//使用串行队列执行同步函数
dispatch_sync(serialQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"current thread====%@ i=====%d",[NSThread currentThread],i);
}
});
结果:
上述结果可以看出,串行队列 + 同步函数 不会产生新的线程, 都是在主线程执行
异步执行串行队列任务
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
dispatch_async(serialQueue, ^{
NSLog(@"current thread====%@ i=====%d",[NSThread currentThread],i);
});
}
上述结果表示 串行队列 + 异步执行方式 会产生一个新的线程,并且是按照顺序执行的,因为是串行队列,任务只能一个一个按照顺序执行
同步执行并行队列任务
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"current thread====%@ i=====%d",[NSThread currentThread],i);
});
}
上述结果表示并行队列 + 同步函数 不会产生新的线程,是在主线程执行
异步执行并行队列任务
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(concurrentQueue, ^{
NSLog(@"current thread====%@ i=====%d",[NSThread currentThread],i);
});
}
上述结果标书, 异步函数 + 并行队列 会产生新的线程,并且是没有顺序的执行的
全局队列 和主队列
系统也为我们提供了两种队列,分别是:全局队列dispatch_get_global_queue(long identifier, unsigned long flags)、主队列dispatch_get_main_queue()。
全局队列
全局队列本质上是一个并发队列,第一个参数是队列是服务质量的选择(优先级), 第二个参数是保留参数暂时传0
/**
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
*/
//获取一个默认优先级的全局队列
dispatch_queue_t systemQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
一般情况下队列默认优先级就可以了,优先级本质上是一个概率问题,优先级越高,CPU调度的概率越高,并不具备确定性。
主队列
主队列不需要参数可以直接获取
dispatch_queue_t mainQueue = dispatch_get_main_queue()
GCD常用函数
比如实际开发中我们需要同步数据,但是这些数据来自于不同的接口,只有所有的信息同步结束之后可以操作,同步信息的操作应该在子线程中进行,我们可以一步并发来执行这些好事操作,但是我们怎么知道所有的任务操作都执行完了?
GCD中Group的用法
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"异步队列1====%d",i);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"异步队列2====%d",i);
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"两个队列都执行完了---");
});
还有另外一种方法:dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"异步队列1====%d",i);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"异步队列2====%d",i);
}
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"两个队列都执行完了---");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"可以做操作了---");
});
});
两种方法的运行结果是相同的,第二种更加灵活一些,但是代码相对较多一点。我们可以更好的对任务进行控制,并且在特定的场景满足我们的需求。灵活使用调度组,可以让我们对线程同步控制更加得心应手
Dispatch Semaphore(队列中的信号值)
GCD 中的信号量是指 Dispatch Semaphore
,是持有计数的信号。
Dispatch Semaphore
提供了三个函数
1.dispatch_semaphore_create
:创建一个Semaphore并初始化信号的总量
2.dispatch_semaphore_signal
:发送一个信号,让信号总量加1
3.dispatch_semaphore_wait
:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。Dispatch Semaphore
在实际开发中主要用于:
保持线程同步,将异步执行任务转换为同步执行任务
保证线程安全,为线程加锁
保持线程同步
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSInteger number = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
number = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %zd",number);
dispatch_semaphore_wait加锁阻塞了当前线程,dispatch_semaphore_signal解锁后当前线程继续执行
保证线程安全,为线程加锁:
在线程安全中可以将dispatch_semaphore_wait看作加锁,而dispatch_semaphore_signal看作解锁
首先创建全局变量
_semaphore = dispatch_semaphore_create(1);
注意到这里的初始化信号量是1
- (void)asyncTask
{
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
count++;
sleep(1);
NSLog(@"执行任务:%zd",count);
dispatch_semaphore_signal(_semaphore);
}
异步并发调用asyncTask
for (NSInteger i = 0; i < 100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self asyncTask];
});
}
然后发现打印是从任务1顺序执行到100,没有发生两个任务同时执行的情况。
原因如下:
在子线程中并发执行asyncTask,那么第一个添加到并发队列里的,会将信号量减1,此时信号量等于0,可以执行接下来的任务。而并发队列中其他任务,由于此时信号量不等于0,必须等当前正在执行的任务执行完毕后调用dispatch_semaphore_signal将信号量加1,才可以继续执行接下来的任务,以此类推,从而达到线程加锁的目的。
dispatch_once_t
block中的内容只执行一次。
for (int i = 0; i < 5; i++) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"回执行几次==");
});
}
dispatch_after
- dispatch_after 基本操作延迟执行
- (void)after {
NSLog(@"------------");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"++++++++");
});
}
结果表示延迟两秒执行
- dispatch_after中执行一些需要注意的方面
dispatch_after 延时操作,不会对控制器强引用, dispatch_after的操作和控制器的消亡没有关系,及时控制器销毁,但是dispatch_after在延迟时间到达后还是会执行操作。如果dispatch_after的block代码块中,对当前控制器产生了强引用,当销毁控制器时,控制器不会立刻销毁,等到dispatch_after中的block代码执行完毕之后,在销毁。
- (void)afterExecture {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//对当前控制器强引用
NSLog(@"延迟10秒执行操作===%@",self.mark);
});
}
没有产生引用的执行代码和结果:
- (void)afterExecture {
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//对当前控制器强引用
NSLog(@"延迟10秒执行操作===%@",weakSelf.mark);
});
}
GCD定时器
/*
第一个参数:source的类型DISPATCH_SOURCE_TYPE_TIMER 表示是定时器
第二个参数:描述信息,线程ID
第三个参数:更详细的描述信息
第四个参数:队列,决定GCD定时器中的任务在哪个线程中执行
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t _Nullable queue);
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
//2.设置定时器(起始时间|间隔时间|精准度)
/*
第一个参数:定时器对象
第二个参数:起始时间,DISPATCH_TIME_NOW 从现在开始计时
第三个参数:间隔时间 2.0 GCD中时间单位为纳秒
第四个参数:精准度 绝对精准0
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"GCD定时器-----");
});
dispatch_resume(timer);
dispatch_barrier
dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"任务1==%d",i);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"任务2==%d",i);
}
});
dispatch_barrier_async(queue, ^{
NSLog(@"截断,等上面任务完之后再无按成下面的任务");
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"任务3==%d",i);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"任务4==%d",i);
}
});
输出结果:
dispatch_barrier 中使用队列 必须要是DISPATCH_QUEUE_CONCURRENT并行队列,不然效果就和dipatch_async/dispatch_sync普通效果一样,没有拦截作用
//使用的是全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"任务1==%d",i);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"任务2==%d",i);
}
});
dispatch_barrier_async(queue, ^{
NSLog(@"截断,等上面任务完之后再无按成下面的任务");
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"任务3==%d",i);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"任务4==%d",i);
}
});
结果表示没并没有起到拦截作用
NSOperation
NSOperation是GCD的面向对象的封装。它拥有GCD的高效,也拥有面向对象的编程思想。NSOperation本身是个基类,我们需要使用他的子类。系统给我们提供了两个分别是:NSBlockOperation和NSInvocationOperation。一种是通过Selector的形式添加操作,一种是block的形式添加操作,
NSBlockOperation
创建NSBlockOperation对象,然后在调用start方法才能执行,但是执行在主线程。
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation === current thread======%@",[NSThread currentThread]);
}];
[blockOperation start];
如果我们在blockOperation的基础之上添加一个操作任务,那这个操作任务会开辟一个新的线程来执行任务:
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation === current thread======%@",[NSThread currentThread]);
}];
//添加任务为开辟的新县城
[blockOperation addExecutionBlock:^{
NSLog(@"addExecution current thread ======%@",[NSThread currentThread]);
}];
[blockOperation start];
只有新添加的操作,才会开辟新的线程执行,要想使blockOperation这个任务也开辟一个新的线程执行任务,那么需要天家到队列中:
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation === current thread======%@",[NSThread currentThread]);
}];
//添加任务为开辟的新县城
[blockOperation addExecutionBlock:^{
NSLog(@"addExecution current thread ======%@",[NSThread currentThread]);
}];
//[blockOperation start];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperation:blockOperation];
添加到队列中的操作,都会开辟一个新的线程 来执行任务。
NSInvocationOperation
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
[invocationOperation start];
- NSInvocationOperation的用法和NSBlockOperation的用法类似。
NSOperationQueue
- NSOperationQueue是一个队列,添加到队列中的对象,都会都会在子线程中执行操作任务,NSOperationQueue可以添加NSBlockOperation,NSInvocationOperation实例对象之外还可以添加block的操作。
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
//添加block操作
[operationQueue addOperationWithBlock:^{
NSLog(@"操作1------%@",[NSThread currentThread]);
}];
[operationQueue addOperationWithBlock:^{
NSLog(@"操作2------%@",[NSThread currentThread]);
}];
[operationQueue addOperationWithBlock:^{
NSLog(@"操作3------%@",[NSThread currentThread]);
}];
运行结果:
- NSOperationQueue并没有全局队列,但我们也可以根据自己的需求来创建全局队列。NSOperationQueue也有获取主队列的类方法[NSOperationQueue mainQueue],用起来也很简单方便,跟GCD中的主队列一样。
- 一个子线程处理耗时的操作,然后刷新UI代码,使用NSOperationQueue是这样实现的:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
//添加block操作
[operationQueue addOperationWithBlock:^{
NSLog(@"子线程处理耗时操作------%@",[NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"主线程更新UI------%@",[NSThread currentThread]);
}];
}];
- 在线程同步上,NSOperation 没有group,但是有操作依赖,一样可以实现同样的效果。它的依赖,是操作的方法,所以如果要使用依赖,我们就得自己创建操作,然后操作之间设置好依赖关系,再把它们丢到队列里,比如说前面GCD中的那个同步信息的例子:
- (void)operationRelpOn {
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务一======");
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务2-=====");
}];
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation1) object:nil];
//设置依赖关系
[invocationOperation addDependency:blockOperation];
[invocationOperation addDependency:blockOperation2];
[operationQueue addOperation:blockOperation];
[operationQueue addOperation:blockOperation2];
[operationQueue addOperation:invocationOperation];
}
- (void)invocationOperation1 {
NSLog(@"任务3=====");
}
这里操作3依赖了操作1、操作2,必须等着两个操作完成之后在完成执行。
在给队列添加操作的时候,我们还有一个方法可以一次性添加多个操作的方法
/**
第一个参数是需要天骄操作的数组集合
第二个参数: 是否等待这些操作任务完成,如果 设置为YES 则必须等待所有任务完成,此操作会阻塞主线程; 如果设置为NO,则不会阻塞主线程,也就是不会等待这些操作完成,就可以往下继续执行
*/
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
- (void)operationRelpOn2 {
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务一======");
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务2-=====");
}];
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation1) object:nil];
//设置依赖关系
[invocationOperation addDependency:blockOperation];
[invocationOperation addDependency:blockOperation2];
[operationQueue addOperations:@[blockOperation,blockOperation2,invocationOperation] waitUntilFinished:NO];
NSLog(@"====================");
}
- (void)invocationOperation1 {
NSLog(@"任务3=====");
}
输出结果:可以发先如果第二个参数设置为NO,并不会等待任务执行完成,反之如果为YES则会阻塞主线程,等待所有任务完成之后,在继续往下执行
虽然这我们不能在主线程中使用这种,但是我们可以在子线程中使用这个参数 设置为YES,可以和在主线程使用设置为NO达到一样的效果
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperationWithBlock:^{
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务一======");
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务2-=====");
}];
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation1) object:nil];
//设置依赖关系
[invocationOperation addDependency:blockOperation];
[invocationOperation addDependency:blockOperation2];
[operationQueue addOperations:@[blockOperation,blockOperation2,invocationOperation] waitUntilFinished:YES];
NSLog(@"任务4===========");
}];
- NSOperationQueue本身并没有并发队列和串行队列的选项,它默认是并发队列,但是它还有一个maxConcurrentOperationCount 属性,这个属性是设置最大并发数量(最多能开几条线程执行操作),如果最大并发数设置为1 ,他就是串行队列的模式。
- NSOperationQueue还可以使用suspended属性来控制队列中的暂停和继续;调用cancelAllOperations() 方法可以取消队列里面的所有操作
- IOS13.0 以后NSOperationQueue新增一个api方法 可以实现GCD中dispatch_barrier(栅栏)效果
- (void)operationBarrier{
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务一======");
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务2-=====");
}];
NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务3-=====");
}];
NSBlockOperation *blockOperation4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务4-=====");
}];
[operationQueue addOperation:blockOperation];
[operationQueue addOperation:blockOperation2];
[operationQueue addBarrierBlock:^{
NSLog(@"拦截成功-----------");
}];
[operationQueue addOperation:blockOperation3];
[operationQueue addOperation:blockOperation4];
}