×

iOS界面卡顿监测

96
RiverLi
2017.10.08 13:38* 字数 944

RunLoop

  1. RunLoop是与线程相关的概念。
  2. 在Cocoa和Core Foundation框架中提供了runloop对象以便帮助你配置和管理线程的runloop。
  3. 每一个线程都有与之相关的runloop对象,只有非主线程需要显示的去启动它的runloop,主线程的runloop随着应用程序的开启的已经被设置和启动了。
  4. 线程和RunLoop之间是一一对应的。其关系保存在一个全局的Dictionary中,线程刚创建时并没有RunLoop,如果你不主动获取,它一直不会有,RunLoop的创建发生在第一次获取时。
  5. CFRunLoopGetCurrent()获取当前线程的RunLoop对象,** CFRunLoopGetMain()**获取主线程的runloop对象。

RunLoop Model

model item

input source:
intput source有两种:source0 表示:Custom Source, source1表示:Port-Based Source
Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。
Timer source:
在预设的时间点同步的传递消息。Timer是线程通知自己做某件事的一种方式。
它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
Observer:
每个 Observer 都包含了一个回调(函数指针),当 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
};

Model comment属性

苹果公开提供的 Mode 有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用这两个 Mode Name 来操作其对应的 Mode。

CFRunLoopMode 和 CFRunLoop 的结构大致如下:

struct __CFRunLoopMode {
    CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"
    CFMutableSetRef _sources0;    // Set
    CFMutableSetRef _sources1;    // Set
    CFMutableArrayRef _observers; // Array
    CFMutableArrayRef _timers;    // Array
    ...
};

struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set
    CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set
    ...
}

这里有个概念叫 “CommonModes”:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。

应用场景举例:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为”Common”属性。(在_commonModes里面)。
DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。

有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 “commonModeItems” 中。”commonModeItems” 被 RunLoop 自动更新到所有具有”Common”属性的 Mode 里去。

runloop执行过程

图片来自Apple开发文档
图片来自Apple开发文档
图片来自阿里百川

图片来自阿里百川
图片来自阿里百川

参考资料

iOS实时卡顿监控
深入理解RunLoop
移动端监控体系之技术原理剖析
关于RunLoop部分源码的注释
什么是 Event Loop
阿里百川码力APP监控来了 重量级选手进入APM市场
apple开发文档-runloop
Run Loop学习笔记和摘抄

iOS开发
Web note ad 1