runloop
- 一个运行循环,保证程序不退出
- 负责处理各种事件(source、timer、observer)
- 没有事件处理则进入休眠,节省资源,有事件则唤醒处理
Core Foundation中关于RunLoop的5个类:
1、CFRunLoopRef - 获得当前RunLoop和主RunLoop
2、CFRunLoopModeRef RunLoop - 运行模式,只能选择一种,在不同模式中做不同的操作
3、CFRunLoopSourceRef - 事件源,输入源
4、CFRunLoopTimerRef - 定时器时间
5、CFRunLoopObserverRef - 观察者
runloop 源
1、输入源:
- NSPort 基于端口的源
- 自定义源
- performSelector:OnThread
2、时间源
runloop mode
- 一个runloop可以有多个mode,每个mode又包含若干个source、timer、observer。
- runloop有5个mode:
- kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
- kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
对外暴露的Mode为:NSDefaultRunLoopMode
和NSRunLoopCommonModes
Q:由于 UITabelView 在滑动的时候,会从当前的 RunLoop 默认的模式 kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 自动切换到 UITrackingRunLoopMode界面追踪模式。这个时候,处于 NSDefaultRunLoopMode 里面的 NSTimer 由于切换了模式造成计时器无法继续运行。
A:
1、更改RunLoop运行Mode(NSRunLoopCommonModes)
2、将NSTimer放到新的线程中
runloop source
- Port-Based Sources (端口)
- Custom Input (自定义事件)
- Cocoa Perform Selector Sources
按照函数的调用栈:
Source0:非基于Port的 用于用户主动触发的事件(点击button 或点击屏幕)
Source1:基于Port的 通过内核和其他线程相互发送消息(与内核相关)
注意:Source1在处理的时候会分发一些操作给Source0去处理
runloop observer
CFRunLoopObserverRef观察者,监听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 应用
- 常驻线程
- AsyncDisplayKit, RunLoop 中添加一个 Observer,监听了runloop状态进行处理渲染
- 卡顿监控(同上)
- 子线程定时器
- performSelector:
当调用 NSObject 的performSelecter:afterDelay:
后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
当调用performSelector:onThread:
时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
AutoreleasePool
App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一个Observer监视Entry(即将进入runloop),其回调会调用_objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个Observer监视BeforeWaiting和Exit。
BeforeWaiting(准备进入休眠)时调用 _objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;
Exit(即将退出runloop)时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
Q:自动释放池什么时候释放?
A:在runloop休眠前释放(kCFRunLoopBeforeWaiting)
网友评论