iOS--RunLoop和NSTimer
一. 什么是Runloop?
runloop就是运行循环,每一个应用程序想要保持活性,都会需要这样一个死循环,并不是iOS特有的,runloop可以保证程序不退出。runloop的每一次循环都需要负责时间的监听,例如定时器,触摸时间,网络事件等。当没有事件发生时,runloop会自动使程序休眠。
二. 理解Runloop。
(1) 观望runloop
以计时器为例子,我们这样创建一个计时器,运行,会打印出“计时器”
NSTimer * timer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updataTimer) userInfo:nil repeats:YES];
-(void)updataTimer
{
NSLog(@"计时器");
}
但是换一种方式创建计时器,那么将不会打印。
NSTimer * timer =[NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updataTimer) userInfo:nil repeats:YES];
因为这个计时器识没有被添加在当前的runloop中,需要我们手动添加。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
timer就是我们刚刚创建的计时器,那么mode
是什么呢?
我们在程序中创建一个表格,运行程序,当我们拖动表格的时候,可以明显的看到,计时器不打印了,就是因为这个mode
模式,接下来介绍这几种模式。
NSDefaultRunLoopMode --默认模式 建议将时钟事件和网络事件添加到这种模式
UITrackingRunLoopMode --这种模式是专门用来处UI事件的
还有两种模式,一个是处理系统事件的模式,一个是初始化程序的模式,这两个我们开发者是无法使用的。
NSRunLoopCommonModes --这个不是一种确切的模式,只是一个占位
当我们把上面的mode
设置为UITrackingRunLoopMode的时候,我们拖动表格计时器才会运行,设置为NSRunLoopCommonModes的时候,计时器在哪种模式下都可以运行。而上面自动添加到runloop中的定时器初始化方法,就是添加在了NSDefaultRunLoopMode模式下。
runloop每循环一次,会在某一个模式下进行切换,去执行这一种模式下的事件。runloop会优先处理UI模式。
(2)触碰runloop(runloop与线程)
通过上面的讲述,我们知道,当创建一个计时器时,放在NSRunLoopCommonModes中是最好的,但是当在这里面做耗时操作界面会不会卡顿呢?如果卡顿该怎么办呢?
会卡顿的,不信你试试!!
我们直接来说该怎么办吧。😢😢
其实马上就可以想到,使用GCD创建子线程就可以了啊,我们来试一下
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSTimer * timer =[NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updataTimer) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
});
-(void)updataTimer
{
NSLog(@"计时器");
}
运行一下可以看到,nstimer的方法不打印了。为什么呢?
以为我们的nstimer被创建在子线程,而在这个线程上面没有runloop循环,所以这个子线程在执行完毕之后被回收了,无法监听timer。
那么我们就需要让这个runloop跑起来。
[[NSRunLoop currentRunLoop] run];
运行,完美解决。这时候即使把mode换成NSDefaultRunLoopMode,拖动表格也不会打扰timer,因为是主线程在处理UI。继续来看,在这句代码下面加一个nslog,是不会打印的,因为这句代码是死循环。而且这种让runloop运行的方法是不被建议的,因为一旦开启,没有办法让其停止。
也有一种low逼的解决办法。既然这是一个死循环,那么我们可以创建一个自己的死循环去解决这个不能停止的问题。
_finish=NO;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSTimer * timer =[NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updataTimer) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
while (!_finish) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
});
-(void)updataTimer
{
static int num = 0;
NSLog(@"计时器");
if (num>9) {
self.finish=YES;
}
}
(3)runloop番外
runloop不管在哪种模式下,都只会监听三种事件。
Source :
CFRunloopSourceRef 事件源(输出源)。
包含 source0 : 非系统事件 source1 : 系统事件
什么是系统事件和非系统事件呢?
简单的写一段代码。
[self performSelector:@selector(eat)];
-(void)eat
{
NSLog(@"吃");
}
在调用处打断点,运行我们可以在调用栈中看到。
屏幕快照 2017-03-23 下午5.04.49.pngObserver :
runloop的观察者
timer :
计时器
(4)计时器番外(GCD定时器)
我们创建一个GCD的计时器并且让其运行起来。
@property(nonatomic,strong)dispatch_source_t timer;
// 创建一个队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 创建一个计时器
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 启动计时器
dispatch_resume(self.timer);
到这里还需要在启动前设置计时器的各种参数,需要用到这个方法dispatch_source_set_timer(<#dispatch_source_t _Nonnull source#>, <#dispatch_time_t start#>, <#uint64_t interval#>, <#uint64_t leeway#>)
第一个参数:我们要进行设置的对象->self.timer
第二个参数:什么时间开始->DISPATCH_TIME_NOW
(立刻开始)
第三个参数:多长时间执行一次,GCD的时间是十分精确的,单位是纳秒,如果我们要设置为一秒->dispatch_time_t interval = 1.0 * NSEC_PER_SEC;
最后一个参数是0。
接下来好要为这个计时器设置一个回调函数。
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"--------%@",[NSThread currentThread]);
});
运行查看打印日志
24F87609-CC8E-41A1-804F-C7CE4CDA6C69.png
GCD中已经为我们封装好了runloop,所以不必考虑mode。