基本做用面试
最下面的UIApplicationMain函数内部就启动了一个RunLoop,因此UIApplicationMain就一直没有返回,保持了程序的持续运行markdown
iOS有两套API来访问和使用RunLoop网络
NSRunLoop和CFRunLoopRef都表明着RunLoop对象 可是NSRunLoop是基于CFRunLoopRef的一层OC包装 ,因此更底层的是CFRunLoopRef异步
是如何保证每条线程有惟一一个对应的RunLoop对象的呢?函数
注意,经过NSRunLoop和CFRunLoopRef获得的RunLoop也仍是不一样的对象,可是能够经过.getCFRunLoop
将NSRunLoop转为CFRunLoopRefoop
子线程的RunLoop直接经过[NSThread currentThread]
方法建立获得,实际上是懒加载的性能
Runloop的五个类atom
这五个类的关系:spa
系统默认注册了五个mode 线程
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self timer];
}
-(void)timer{
//1. 建立计时器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//2. 将计时器添加到runloop中
//第一个参数是计时器,第二个参数是runloop的mode,这里选择默认mode
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
}
-(void)run{
NSLog(@"run---%@----%@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode);
}
复制代码
当你点击模拟器时,打印结果以下,能够看到每隔2s进行一次打印
2021-03-10 10:37:05.571371+0800 runloop1[1771:51979] run---<NSThread: 0x600001f44a00>{number = 1, name = main}----kCFRunLoopDefaultMode
2021-03-10 10:37:07.572174+0800 runloop1[1771:51979] run---<NSThread: 0x600001f44a00>{number = 1, name = main}----kCFRunLoopDefaultMode
2021-03-10 10:37:09.571467+0800 runloop1[1771:51979] run---<NSThread: 0x600001f44a00>{number = 1, name = main}----kCFRunLoopDefaultMode
复制代码
可是当你向storyboard中添加一个textView会发生什么状况呢?
咱们来看看场景二
你会发现当你点击背景时,正常每隔2s进行打印,可是当你滑动textView时,打印中止,且你再中止滑动,打印又从新开始
这是为何?
解决方法,修改runloop的mode类型,改成UITrackingRunLoopMode(界面追踪模式)
-(void)timer{
//1. 建立计时器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//2. 将计时器添加到runloop中
[[NSRunLoop currentRunLoop]addTimer:timer forMode:UITrackingRunLoopMode];
}
复制代码
从新运行,会发现当你改成滑动textView时,打印结果以下
2021-03-10 10:55:22.039501+0800 runloop1[2084:70476] run---<NSThread: 0x600002eb4880>{number = 1, name = main}----UITrackingRunLoopMode
2021-03-10 10:55:22.367009+0800 runloop1[2084:70476] run---<NSThread: 0x600002eb4880>{number = 1, name = main}----UITrackingRunLoopMode
复制代码
能够看到此时的模式是界面追踪模式
那么如何既点击view时启动计时器打印,拖动textview时也打印呢?
接下来进入下一场景
但愿达到 既点击view时启动计时器打印,拖动textview时也打印 有两种方式:
-(void)timer{
//1. 建立计时器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//2. 将计时器添加到runloop中
[[NSRunLoop currentRunLoop]addTimer:timer forMode:UITrackingRunLoopMode];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
}
复制代码
打印结果以下
2021-03-10 11:01:20.588388+0800 runloop1[2269:79479] run---<NSThread: 0x60000038c240>{number = 1, name = main}----kCFRunLoopDefaultMode
2021-03-10 11:01:22.588833+0800 runloop1[2269:79479] run---<NSThread: 0x60000038c240>{number = 1, name = main}----kCFRunLoopDefaultMode
2021-03-10 11:01:24.588094+0800 runloop1[2269:79479] run---<NSThread: 0x60000038c240>{number = 1, name = main}----UITrackingRunLoopMode
2021-03-10 11:01:26.588974+0800 runloop1[2269:79479] run---<NSThread: 0x60000038c240>{number = 1, name = main}----UITrackingRunLoopMode
复制代码
NSRunLoopCommonModes = kCFRunLoopDefaultMode + UITrackingRunLoopMode
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
复制代码
定时器的建立有两种方式,上面的场景中使用的是第一种,也就是timerWithTimeInterval
的方式,接下来的场景咱们使用第二种方式scheduledTimerWithTimeInterval
回忆一下咱们刚刚上面建立定时器的方法还须要本身将定时器添加到runloop当中,可是scheduledTimerWithTimeInterval建立的定时器是不须要这样的,系统会帮你作,而且设置运行模式位默认mode
-(void)timer2{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
}
复制代码
还有一个问题,若是咱们的timer是在子线程中建立的,会出现什么问题?
这是为何呢?缘由很简单
-(void)timer2{
//1. 建立子线程的runloop
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
//2. 建立计时器
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//3. 启动runloop
[currentRunLoop run];
}
复制代码
重点来看这个常驻线程
这时候就须要咱们的常驻线程了
注意,咱们想要达到的目的是让一个线程不在他的任务执行完毕后就死亡,而是进入等待模式,在须要时再从新使用该线程
解决方法:
接下来进入具体应用状况
定义三个按钮以下
@property(nonatomic,strong) NSThread *thread;
复制代码
- (IBAction)createClickBtn:(id)sender {
// 建立线程
self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(createRunLoop) object:nil];
[self.thread start];
}
复制代码
建立runloop有两种设置方式
- (void)createRunLoop{
//1. 得到子线程对应的runloop
NSRunLoop *currentLoop = [NSRunLoop currentRunLoop];
//2. 在runloop中设置一个timer
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//3. 将timer添加到runloop中
[currentLoop addTimer:timer forMode:NSDefaultRunLoopMode];
//4. 启动runloop
[currentLoop run];
}
复制代码
- (void)createRunLoop{
//1. 得到子线程对应的runloop
NSRunLoop *currentLoop = [NSRunLoop currentRunLoop];
//2. 在runloop中设置一个source
[currentLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
//3. 启动runloop
[currentLoop run];
}
复制代码
- (IBAction)task1ClickBtn:(id)sender {
[self performSelector:@selector(task1) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (IBAction)task2ClickBtn:(id)sender {
[self performSelector:@selector(task2) onThread:self.thread withObject:nil waitUntilDone:YES];
}
-(void)task1{
NSLog(@"task1---%@",[NSThread currentThread]);
}
-(void)task2{
NSLog(@"task2---%@",[NSThread currentThread]);
}
//这个run函数是用于计时器状况时的
-(void)run{
NSLog(@"%s",__func__);
}
复制代码
运行结果以下
能够看到当你交换点击两个按钮时,任务也是交替执行的,而且是在同一线程下
例如一个网络请求,由于网络请求是异步的,且比较耗时,因此咱们能够建立一个子线程来负责网络请求的功能