iOS-底层原理|iOS-底层原理 27(GCD 之 NSThread & GCD & NSOperation)
iOS 底层原理 文章汇总本文的主要目的是介绍
NSThread
、GCD
、NSOperation
常见的使用方式NSthread
NSthread
是苹果官方提供面向对象的线程操作技术,是对thread
的上层封装,比较偏向于底层。简单方便,可以直接操作线程对象,使用频率较少。创建线程
线程的创建方式主要以下三种方式
- 通过
init
初始化方式创建
- 通过
detachNewThreadSelector
构造器方式创建
- 通过
performSelector...
方法创建,主要是用于获取主线程
,以及后台线程
//1、创建
- (void)cjl_createNSThread{
NSString *threadName1 = @"NSThread1";
NSString *threadName2 = @"NSThread2";
NSString *threadName3 = @"NSThread3";
NSString *threadNameMain = @"NSThreadMain";
//方式一:初始化方式,需要手动启动
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:threadName1];
[thread1 start];
//方式二:构造器方式,自动启动
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:threadName2];
//方式三:performSelector...方法创建
[self performSelectorInBackground:@selector(doSomething:) withObject:threadName3];
//方式四
[self performSelectorOnMainThread:@selector(doSomething:) withObject:threadNameMain waitUntilDone:YES];
}
- (void)doSomething:(NSObject *)objc{
NSLog(@"%@ - %@", objc, [NSThread currentThread]);
}
属性
- thread.isExecuting//线程是否在执行
- thread.isCancelled//线程是否被取消
- thread.isFinished//是否完成
- thread.isMainThread//是否是主线程
- thread.threadPriority //线程的优先级,取值范围0.0-1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高
类方法
常用的类方法有以下几个:
currentThread
:获取当前线程
sleep...
:阻塞线程
exit
:退出线程
mainThread
:获取主线程
- (void)cjl_NSThreadClassMethod{
//当前线程
[NSThread currentThread];
// 如果number=1,则表示在主线程,否则是子线程
NSLog(@"%@", [NSThread currentThread]);
//阻塞休眠
[NSThread sleepForTimeInterval:2];
//休眠多久
[NSThread sleepUntilDate:[NSDate date]];
//休眠到指定时间//其他
[NSThread exit];
//退出线程
[NSThread isMainThread];
//判断当前线程是否为主线程
[NSThread isMultiThreaded];
//判断当前线程是否是多线程
NSThread *mainThread = [NSThread mainThread];
//主线程的对象
NSLog(@"%@", mainThread);
GCD dispatch_after
- (void)cjl_testAfter{
/*
dispatch_after表示在某队列中的block延迟执行
应用场景:在主队列上延迟执行一项任务,如viewDidload之后延迟1s,提示一个alertview(是延迟加入到队列,而不是延迟执行)
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2s后输出");
});
}
dispatch_once
- (void)cjl_testOnce{
/*
dispatch_once保证在App运行期间,block中的代码只执行一次
应用场景:单例、method-Swizzling
*/
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//创建单例、method swizzled或其他任务
NSLog(@"创建单例");
});
}
dispatch_apply
- (void)cjl_testApply{
/*
dispatch_apply将指定的Block追加到指定的队列中重复执行,并等到全部的处理执行结束——相当于线程安全的for循环应用场景:用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性
- 添加到串行队列中——按序执行
- 添加到主队列中——死锁
- 添加到并发队列中——乱序执行
- 添加到全局队列中——乱序执行
*/dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_SERIAL);
NSLog(@"dispatch_apply前");
/**
param1:重复次数
param2:追加的队列
param3:执行任务
*/
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"dispatch_apply 的线程 %zu - %@", index, [NSThread currentThread]);
});
NSLog(@"dispatch_apply后");
}
dispatch_group_t
有以下两种使用方式
- 【方式一】使用
dispatch_group_async + dispatch_group_notify
- (void)cjl_testGroup1{
/*
dispatch_group_t:调度组将任务分组执行,能监听任务组完成,并设置等待时间应用场景:多个接口请求之后刷新页面
*/dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"请求一完成");
});
dispatch_group_async(group, queue, ^{
NSLog(@"请求二完成");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新页面");
});
}
- 【方式二】使用
dispatch_group_enter + dispatch_group_leave + dispatch_group_notify
- (void)cjl_testGroup2{
/*
dispatch_group_enter和dispatch_group_leave成对出现,使进出组的逻辑更加清晰
*/
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"请求一完成");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"请求二完成");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新界面");
});
}
- 在方式二的基础上增加超时
dispatch_group_wait
- (void)cjl_testGroup3{
/*
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)group:需要等待的调度组
timeout:等待的超时时间(即等多久)
- 设置为DISPATCH_TIME_NOW意味着不等待直接判定调度组是否执行完毕
- 设置为DISPATCH_TIME_FOREVER则会阻塞当前调度组,直到调度组执行完毕返回值:为long类型
- 返回值为0——在指定时间内调度组完成了任务
- 返回值不为0——在指定时间内调度组没有按时完成任务*/
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"请求一完成");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"请求二完成");
dispatch_group_leave(group);
});
//long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW);
//long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 *NSEC_PER_SEC));
NSLog(@"timeout = %ld", timeout);
if (timeout == 0) {
NSLog(@"按时完成任务");
}else{
NSLog(@"超时");
}dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新界面");
});
}
dispatch_barrier_sync & dispatch_barrier_async
栅栏函数,主要有两种使用场景:串行队列、并发队列
- (void)cjl_testBarrier{
/*
dispatch_barrier_sync & dispatch_barrier_async应用场景:同步锁等栅栏前追加到队列中的任务执行完毕后,再将栅栏后的任务追加到队列中。
简而言之,就是先执行栅栏前任务,再执行栅栏任务,最后执行栅栏后任务- dispatch_barrier_async:前面的任务执行完毕才会来到这里
- dispatch_barrier_sync:作用相同,但是这个会堵塞线程,影响后面的任务执行- dispatch_barrier_async可以控制队列中任务的执行顺序,
- 而dispatch_barrier_sync不仅阻塞了队列的执行,也阻塞了线程的执行(尽量少用)
*/[self cjl_testBarrier1];
[self cjl_testBarrier2];
}
- (void)cjl_testBarrier1{
//串行队列使用栅栏函数dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_SERIAL);
NSLog(@"开始 - %@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2s的任务1 - %@", [NSThread currentThread]);
});
NSLog(@"第一次结束 - %@", [NSThread currentThread]);
//栅栏函数的作用是将队列中的任务进行分组,所以我们只要关注任务1、任务2
dispatch_barrier_async(queue, ^{
NSLog(@"------------栅栏任务------------%@", [NSThread currentThread]);
});
NSLog(@"栅栏结束 - %@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2s的任务2 - %@", [NSThread currentThread]);
});
NSLog(@"第二次结束 - %@", [NSThread currentThread]);
}
- (void)cjl_testBarrier2{
//并发队列使用栅栏函数dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"开始 - %@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2s的任务1 - %@", [NSThread currentThread]);
});
NSLog(@"第一次结束 - %@", [NSThread currentThread]);
//由于并发队列异步执行任务是乱序执行完毕的,所以使用栅栏函数可以很好的控制队列内任务执行的顺序
dispatch_barrier_async(queue, ^{
NSLog(@"------------栅栏任务------------%@", [NSThread currentThread]);
});
NSLog(@"栅栏结束 - %@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2s的任务2 - %@", [NSThread currentThread]);
});
NSLog(@"第二次结束 - %@", [NSThread currentThread]);
}
dispatch_semaphore_t
信号量主要用作同步锁,用于控制GCD最大并发数
- (void)cjl_testSemaphore{
/*
应用场景:同步当锁, 控制GCD最大并发数- dispatch_semaphore_create():创建信号量
- dispatch_semaphore_wait():等待信号量,信号量减1。当信号量< 0时会阻塞当前线程,根据传入的等待时间决定接下来的操作——如果永久等待将等到信号(signal)才执行下去
- dispatch_semaphore_signal():释放信号量,信号量加1。当信号量>= 0 会执行wait之后的代码*/
dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0;
i < 10;
i++) {
dispatch_async(queue, ^{
NSLog(@"当前 - %d, 线程 - %@", i, [NSThread currentThread]);
});
}//利用信号量来改写
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i = 0;
i < 10;
i++) {
dispatch_async(queue, ^{
NSLog(@"当前 - %d, 线程 - %@", i, [NSThread currentThread]);
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
}
dispatch_source_t
dispatch_source_t
主要用于计时操作,其原因是因为它创建的timer不依赖于RunLoop
,且计时精准度比NSTimer
高- (void)cjl_testSource{
/*
dispatch_source应用场景:GCDTimer
在iOS开发中一般使用NSTimer来处理定时逻辑,但NSTimer是依赖Runloop的,而Runloop可以运行在不同的模式下。如果NSTimer添加在一种模式下,当Runloop运行在其他模式下的时候,定时器就挂机了;又如果Runloop在阻塞状态,NSTimer触发时间就会推迟到下一个Runloop周期。因此NSTimer在计时上会有误差,并不是特别精确,而GCD定时器不依赖Runloop,计时精度要高很多dispatch_source是一种基本的数据类型,可以用来监听一些底层的系统事件
- Timer Dispatch Source:定时器事件源,用来生成周期性的通知或回调
- Signal Dispatch Source:监听信号事件源,当有UNIX信号发生时会通知
- Descriptor Dispatch Source:监听文件或socket事件源,当文件或socket数据发生变化时会通知
- Process Dispatch Source:监听进程事件源,与进程相关的事件通知
- Mach port Dispatch Source:监听Mach端口事件源
- Custom Dispatch Source:监听自定义事件源主要使用的API:
- dispatch_source_create: 创建事件源
- dispatch_source_set_event_handler: 设置数据源回调
- dispatch_source_merge_data: 设置事件源数据
- dispatch_source_get_data: 获取事件源数据
- dispatch_resume: 继续
- dispatch_suspend: 挂起
- dispatch_cancle: 取消
*///1.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//2.创建timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//3.设置timer首次执行时间,间隔,精确度
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0*NSEC_PER_SEC, 0.1*NSEC_PER_SEC);
//4.设置timer事件回调
dispatch_source_set_event_handler(timer, ^{
NSLog(@"GCDTimer");
});
//5.默认是挂起状态,需要手动激活
dispatch_resume(timer);
}
NSOperation
NSOperation
是基于GCD之上的更高一层封装,NSOperation需要配合NSOperationQueue
来实现多线程
。NSOperatino实现多线程的步骤如下:
- 1、
创建任务
:先将需要执行的操作封装到NSOperation对象中。
- 2、
创建队列
:创建NSOperationQueue。
- 3、
将任务加入到队列中
:将NSOperation对象添加到NSOperationQueue中。
//基本使用
- (void)cjl_testBaseNSOperation{
//处理事务
NSInvocationOperation *op =[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation::) object:@"CJL"];
//创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//操作加入队列
[queue addOperation:op];
}
- (void)handleInvocation:(id)operation{
NSLog(@"%@ - %@", operation, [NSThread currentThread]);
}
需要注意的是,NSOperation是个抽象类,实际运用时中需要使用它的子类,有三种方式:
- 1、使用子类
NSInvocationOperation
//直接处理事务,不添加隐性队列
- (void)cjl_createNSOperation{
//创建NSInvocationOperation对象并关联方法,之后start。
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@"CJL"];
[invocationOperation start];
}
- 2、使用子类
NSBlockOperation
- (void)cjl_testNSBlockOperationExecution{
//通过addExecutionBlock这个方法可以让NSBlockOperation实现多线程。
//NSBlockOperation创建时block中的任务是在主线程执行,而运用addExecutionBlock加入的任务是在子线程执行的。
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"main task = >currentThread: %@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"task1 = >currentThread: %@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"task2 = >currentThread: %@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"task3 = >currentThread: %@", [NSThread currentThread]);
}];
[blockOperation start];
}
- 3、定义继承自
NSOperation的子类
,通过实现内部相应的方法来封装任务。
//*********自定义继承自NSOperation的子类*********
@interface CJLOperation : NSOperation
@end@implementation CJLOperation
- (void)main{
for (int i = 0;
i < 3;
i++) {
NSLog(@"NSOperation的子类:%@",[NSThread currentThread]);
}
}
@end//*********使用*********
- (void)cjl_testCJLOperation{
//运用继承自NSOperation的子类 首先我们定义一个继承自NSOperation的类,然后重写它的main方法。
CJLOperation *operation = [[CJLOperation alloc] init];
[operation start];
}
NSOperationQueue
NSOperationQueue添加事务
NSOperationQueue
有两种队列:主队列
、其他队列
。其他队列包含了 串行和并发
。- 主队列:
主队列
上的任务是在主线程
执行的。
- 其他队列(非主队列):加入到'非队列'中的任务
默认就是并发
,开启多线程。
- (void)cjl_testNSOperationQueue{
/*
NSInvocationOperation和NSBlockOperation两者的区别在于:
- 前者类似target形式
- 后者类似block形式——函数式编程,业务逻辑代码可读性更高NSOperationQueue是异步执行的,所以任务一、任务二的完成顺序不确定
*/
// 初始化添加事务
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务1————%@",[NSThread currentThread]);
}];
// 添加事务
[bo addExecutionBlock:^{
NSLog(@"任务2————%@",[NSThread currentThread]);
}];
// 回调监听
bo.completionBlock = ^{
NSLog(@"完成了!!!");
};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo];
NSLog(@"事务添加进了NSOperationQueue");
}
设置执行顺序
//执行顺序
- (void)cjl_testQueueSequence{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0;
i < 5;
i++) {
[queue addOperationWithBlock:^{
NSLog(@"%@---%d", [NSThread currentThread], i);
}];
}
}
【iOS-底层原理|iOS-底层原理 27(GCD 之 NSThread & GCD & NSOperation)】设置优先级
- (void)cjl_testOperationQuality{
/*
NSOperation设置优先级只会让CPU有更高的几率调用,不是说设置高就一定全部先完成
- 不使用sleep——高优先级的任务一先于低优先级的任务二
- 使用sleep进行延时——高优先级的任务一慢于低优先级的任务二
*/
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0;
i < 5;
i++) {
//sleep(1);
NSLog(@"第一个操作 %d --- %@", i, [NSThread currentThread]);
}
}];
// 设置最高优先级
bo1.qualityOfService = NSQualityOfServiceUserInteractive;
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0;
i < 5;
i++) {
NSLog(@"第二个操作 %d --- %@", i, [NSThread currentThread]);
}
}];
// 设置最低优先级
bo2.qualityOfService = NSQualityOfServiceBackground;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo1];
[queue addOperation:bo2];
}
设置并发数
//设置并发数
- (void)cjl_testOperationMaxCount{
/*
在GCD中只能使用信号量来设置并发数
而NSOperation轻易就能设置并发数
通过设置maxConcurrentOperationCount来控制单次出队列去执行的任务数
*/
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"Felix";
queue.maxConcurrentOperationCount = 2;
for (int i = 0;
i < 5;
i++) {
[queue addOperationWithBlock:^{ // 一个任务
[NSThread sleepForTimeInterval:2];
NSLog(@"%d-%@",i,[NSThread currentThread]);
}];
}
}
添加依赖
//添加依赖
- (void)cjl_testOperationDependency{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
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];
[queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
NSLog(@"执行完了?我要干其他事");
}
线程间通讯
//线程间通讯
- (void)cjl_testOperationNoti{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"Felix";
[queue addOperationWithBlock:^{
NSLog(@"请求网络%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"刷新UI%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
}];
}];
}
推荐阅读
- 做一件事情的基本原理是什么()
- 【读书笔记】贝叶斯原理
- SG平滑轨迹算法的原理和实现
- “写作宝典”《金字塔原理》之读书笔记
- 医生随笔(232)不要轻易得罪底层人
- Spring|Spring 框架之 AOP 原理剖析已经出炉!!!预定的童鞋可以识别下发二维码去看了
- Spring|Spring Boot 自动配置的原理、核心注解以及利用自动配置实现了自定义 Starter 组件
- iOS-Swift-map|iOS-Swift-map filter reduce、函数式编程
- Vue源码分析—响应式原理(二)
- MYSQL主从同步的实现