RunLoop概念
人如其名,RunLoooooooooooooooop,像是一个死循环,不停的跑圈,永不懈怠。除非程序不启动,或者代码写的太差,以至于crash, RunLoop才不得不停止了。
可是程序启动与否和 RunLoop有什么关系?
程序启动伊始,有一段代码:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([YourAppDelegate class]));
}
这段代码永远不会执行结束,不然程序也停止了。所以RunLoop伴随着程序的启动,一直存在。
当然,RunLoop不可能无意义的瞎跑。RunLoop贯穿整个程序,在奔跑的过程中帮忙处理各种事情。
RunLoop 一直在跑,到底做了啥?
在大部分的开发中主要是和UIKit和Foundation各种类打交道,丝毫没有注意RunLoop的存在。但是事件响应,各种手势识别,定时器等都需要RunLoop进行处理。
RunLoop和线程之间的关系
表面上看很多事情都线程做的,比如页面刷新是主线程做的,异步线程下载,和RunLoop没啥关系。但是线程都是和RunLoop绑定在一起的。每个线程都有一个对应的RunLoop对象。当然不同线程的RunLoop是有区别的,主线程(也是你们常说的UI线程)的 Runloop 会在应用启动时完成启动,其他线程的 Runloop 默认并不会启动,只是在需要使用时,你们手动启动。
每一个线程都有一个RunLoop,但默认情况下只有主线程的才会开启。
RunLoop不同的Mode
不仅每个线程都有RunLoop,而且每一个 RunLoop都包含若干个 Mode。
为什么要有不同的Mode
平时穿的鞋子跑步鞋,休闲鞋,皮鞋,不同的鞋有自己的偏重点,RunLoop也是一样。像穿鞋一样,每次调用RunLoop的主函数的时候,只能指定其中一个Mode,这个Mode被称为CurrentMode。如果需要切换Model,只能退出RunLoop,重新指定一个。这就和换鞋子一样,必须脱掉旧的鞋子才能穿上新的鞋子。
每个Mode包含Source/Timer/Observer,这些都属于Mode的item,item不同,Mode也不同,RunLoop分为五类。
NSDefaultRunLoopMode //大多数工作中默认的运行方式
NSConnectionReplyMode //使用这个Mode去监听NSConnection对象的状态,我们很少需要自己使用这个Mode
NSModalPanelRunLoopMode //在Model Panel情况下去区分事件(OS X开发中会遇到)
NSEventTrackingRunLoopMode //跟踪来自用户交互的事件(比如UITableView上下滑动)
NSRunLoopCommonModes //这是一个伪模式,其为一组run loop mode的集合
iOS中公开暴露出来的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonModes。 NSRunLoopCommonModes 实际上是一个 Mode 的集合,默认包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode。
主线程的 RunLoop 里有两个预置的 Mode:NSDefaultRunLoopMode和 NSEventTrackingRunLoopMode。这两个 Mode 都已经被标记为"Common"属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。
但如果我想滑动ScrollView时,不影响Timer咋办?
既然在DefaultMode下,不影响Timer,TrackingRunLoopMode下能滑动,两者都不影响,就是两种模式都要就ok了,iOS中的commonModeItems就是就是将DefaultMode和TrackingRunLoopMode组合在一起了,因此只用切换到commonModeItems就ok了。
RunLoop的内部逻辑
Runloop也是受系统管理的,NSRunLoop实际只是OC对Runloop简单的封装,Runloop的底层是C语言库CFRunLoop,这里面有一个叫CFRunLoopObserverRef的观察者,也就是前面提到的Observer,当Runloop状态发生改变时,观察者就会记录Runloop的变化。
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Loop
};
RunLoop与NSTimer的准时触发
RunLoop与NSTimer有什么关系呢?
NSTimer其实是一种资源,但它要想起作用必须添加到RunLoop中。
NSTimer会是准时触发事件吗?
timer不是一种实时的机制,会存在延迟,而且延迟的程度跟当前线程的执行情况有关。
正常情况下,你指定一个事件2秒之后触发,但若是此时恰好有一个大规模的连续耗时运算,那timer的执行必然要等到该连续事件处理结束才会开始执行,此时你就无法保证NSTimer的准时触发了。当然这只是针对于一次执行的timer,
[NSTimer scheduledTimerWithTimeInterval:1 target:testObject2 selector:@selector(timerAction:) userInfo:nil repeats:YES];
对于重复性事件,情况也不一样。比如一个程序,你设置了周期性1秒触发,但是有个耗时事件用时两秒,此时就无法准确触发,并且以后会随着这个延迟继续延迟。
RunLoop应用
1.AutoreleasePool
在App启动后,会在主线程度的RunLoop帮我们创建两个Observer。
第一个 Observer 只监视了一个事件:监听事件在Entry(即将进入Loop)期间,其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。
第二个 Observer 监视了两个事件:在BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的,被这些inputSource和timeSource包裹。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
网友评论