RunLoop 一张图就够了

2,128 阅读3分钟

0. 前言

RunLoop详解的文章很多,其实大都来源于那么一两篇,本篇就不再赘述,相关文章会在文末标出。

把相关知识整理成了一张图,看起来也方便一点。

直接看理论可能不太好理解,那么就跟着先来简单感受一下RunLoop吧!

1. 感受 RunLoop

简单地说,RunLoop 就是一个 do-while 循环,反复处理事件。


1.1 RunLoop何时出现?

在 main 函数和 appdelegate 内打断点,观察何时出现 runloop

main函数
程序启动
程序启动时的堆栈

打印结果:

main 函数开始
RunLoopMode: kCFRunLoopDefaultMode
<CFRunLoop 0x6000001f4700 [0x10e6c7c80]> {......}

我们发现:
1.没有打印“main 函数结束”;
2.app 启动后出现了 runloop

这是因为在 app 启动过程中,app 会自动在主线程创建并运行 runloop,循环使 app 得以持续运行,所以 main 不会 return。


1.2 窥探 runloop 的结构

上面 👆的打印结果中<CFRunLoop 0x6000001f4700 [0x10e6c7c80]> {...}括号内可以看到主线程 runloop 的大致结构, 我们可以由此观察到 runloop 的结构。

打印结果精简如下:

CFRunLoop {
        current mode = kCFRunLoopDefaultMode,
        common modes = {
            UITrackingRunLoopMode
            kCFRunLoopDefaultMode
        },
        common mode items ={
            
            // sources0 (manual)
            CFRunLoopSource     { order = -2, callout = __handleHIDEventFetcherDrain}
            CFRunLoopSource     { order = -1, callout = __handleEventQueue}
            CFRunLoopSource     { order = -1, callout = PurpleEventSignalCallback}
            CFRunLoopSource     { order = 0,  callout = FBSSerialQueueRunLoopSourceHandler}
            
            // sources1 (mach port)
            CFRunLoopSource     { order = 0,  port = 14107}
            CFRunLoopSource     { order = 0,  port = 41987}
            CFRunLoopSource     { order = -1, callout = PurpleEventCallback}
            
            // observers
            CFRunLoopObserver   { order = -2147483647, activities = 0x1,  callout = _wrapRunLoopWithAutoreleasePoolHandler}
            CFRunLoopObserver   { order = 0,           activities = 0x20, callout = _UIGestureRecognizerUpdateObserver}
            CFRunLoopObserver   { order = 1999000,     activities = 0xa0, callout = _beforeCACommitHandler}
            CFRunLoopObserver   { order = 2000000,     activities = 0xa0, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
            CFRunLoopObserver   { order = 2001000,     activities = 0xa0, callout = _afterCACommitHandler}
            CFRunLoopObserver   { order = 2147483647,  activities = 0xa0, callout = _wrapRunLoopWithAutoreleasePoolHandler}
            // activities: 0x1 == Entry ; 0x20 == BeforeWaiting ; 0xa0 == BeforeWaiting | Exit
        }
        ,
        modes = {
            UITrackingRunLoopMode {
                sources0 = {/* 同 common mode items 内的 source0 */}
                sources1 = {/* 同 common mode items 内的 source1 */}
                observers = {/* 同 common mode items 内的 observers */}
                timers = (null),
            }
            GSEventReceiveRunLoopMode {
                sources0 = {CFRunLoopSource { order = -1, callout = PurpleEventSignalCallback}}
                sources1 = {CFRunLoopSource { order = -1, callout = PurpleEventCallback}}
                observers = (null),
                timers = (null),
            }
            kCFRunLoopDefaultMode {
                sources0 = {/* 同 common mode items 内的 source0 */}
                sources1 = {/* 同 common mode items 内的 source1 */}
                observers = {/* 同 common mode items 内的 observers */}
                timers = {CFRunLoopTimer {firing = No, interval = 0, tolerance = 0, next fire date = 552971546 (-10.567408 @ 89128060961915), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (UIKit.framework/UIKit)}}
            }
            kCFRunLoopCommonModes {
                sources0 = (null),
                sources1 = (null),
                observers = (null),
                timers = (null),
            }
        }
    }

可以看到,主线程的 runloop 当前处于kCFRunLoopDefaultMode,注册了四种modes

结合上面和源码可以精简出线程的 runloop 大致结构:

struct __CFRunLoop {
    CFMutableSetRef _commonModes;// 具有'common'属性的 modes
    CFMutableSetRef _commonModeItems;// 同步到'common'modes的 items
    CFRunLoopModeRef _currentMode;// 当前 runloop 所处的 mode
    CFMutableSetRef _modes;// runloop 注册的 modes
    ...
};

贴张大神的图以便理解:

runloop结构1

  • 一条线程对应一个runloop
  • 一个runloop含有多个mode
  • 一个mode内可以含有多个soucretimerobserver

1.3 runloop 在 app 静止时在干嘛?

静止时,点暂停:

静止

发现在没有事件处理时,线程的runloop都停留在mach_msg_trap状态, 等待事件发生。(像是布了个陷阱,等待猎物上钩,一上钩就立马处理)


2. 一张图就够了

RunLoop 一张图就够了

3. 实践代码

有空再补吧...

参考:
源码
官方文档 Run Loops
深入理解RunLoop
iOS线下分享《RunLoop》