runloop

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:
  1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
  4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
  5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode

对外暴露的Mode为:NSDefaultRunLoopModeNSRunLoopCommonModes

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)

你可能感兴趣的:(runloop)