runloop - ShenYj/ShenYj.github.io GitHub Wiki
RunLoop 与线程一一对应,系统会有一个全局字典来存储 RunLoop,以线程为 key, RunLoop 为 value,会在第一次获取 RunLoop 时创建 RunLoop
-
CF-1153.18源码中__CFRunLoop的定义struct __CFRunLoop { CFRuntimeBase _base; pthread_mutex_t _lock; /* locked for accessing mode list */ __CFPort _wakeUpPort; // used for CFRunLoopWakeUp Boolean _unused; volatile _per_run_data *_perRunData; // reset for runs of the run loop pthread_t _pthread; uint32_t _winthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; // 重点 -----> CFRunLoopModeRef _currentMode; // `struct __CFRunLoopMode *` 类型指针 CFMutableSetRef _modes; // 一个 `Mode` 集合 // <----- 重点 struct _block_item *_blocks_head; struct _block_item *_blocks_tail; CFAbsoluteTime _runTime; CFAbsoluteTime _sleepTime; CFTypeRef _counterpart; }; typedef struct __CFRunLoopMode *CFRunLoopModeRef; struct __CFRunLoopMode { CFRuntimeBase _base; pthread_mutex_t _lock; /* must have the run loop locked before locking this */ CFStringRef _name; Boolean _stopped; char _padding[3]; // 四种 `Mode` 集合 CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; CFMutableDictionaryRef _portToV1SourceMap; __CFPortSet _portSet; CFIndex _observerMask; #if USE_DISPATCH_SOURCE_FOR_TIMERS dispatch_source_t _timerSource; dispatch_queue_t _queue; Boolean _timerFired; // set to true by the source when a timer has fired Boolean _dispatchTimerArmed; #endif #if USE_MK_TIMER_TOO mach_port_t _timerPort; Boolean _mkTimerArmed; #endif #if DEPLOYMENT_TARGET_WINDOWS DWORD _msgQMask; void (*_msgPump)(void); #endif uint64_t _timerSoftDeadline; /* TSR */ uint64_t _timerHardDeadline; /* TSR */ };
-
CFRunLoopModeRef代表着RunLoop的运行模式 - 一个
RunLoop包含若干个Mode,每个Mode又包含若干个_sources0、_sources1、_observer、_timer- 不同组的
_sources0、_sources1、_observer、_timer能分隔开来,互不影响 - 每一个
_sources0、_sources1、_observer、_timer都是一个集合,可以添加多个source
- 不同组的
-
RunLoop启动时只能选择其中一种Mode,作为curentMode - 如果要切换
Mode只能退出当前Loop,再重选择一个Mode进入 (切换Mode不会造成 App 退出) - 如果
Mode里没有任何_sources0、_sources1、_observer、_timer,RunLoop会立马退出
-
Source0- 触摸事件处理,比如常规的
touchBegan -
Foundation的performSelector:onThread:都是Source0
- 触摸事件处理,比如常规的
-
Source1- 基于
port通信 - 系统捕捉事件,默认触摸事件就是先由Source1捕捉,在分发到Source0处理
- 基于
-
TimersNSTimerperformSelector:withObject:afterDelay:
-
Observers- 用于监听
RunLoop的状态 - UI 界面的刷新
BeforeWaiting autoRelease pool
- 用于监听
-
RunLoop Observer可以监听到的几种状态/* Run Loop Observer Activities */ 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 kCFRunLoopAllActivities = 0x0FFFFFFFU // 以上所有状态 };
可以通过 CoreFoundation 提供的 api 监听 RunLoop 的状态
-
RunLoop执行流程01. 通知 Observers: 进入 Loop --> __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry) ┏━> 02. 通知 Observers: 即将处理 Timers ┃ 03. 通知 Observers: 即将处理 Sources ┃ 04. 处理 Blocks ┃ 05. 处理 Source0 (可能会再次处理 Blocks) ┃ 06. 如果存在 Source1, 就跳转到 `第8步` ━━━━┓ ┃ 07. 通知 Observers,开始休眠(等待消息唤醒) ┃ ┃ 08. 通知 Observers,结束休眠(被某个消息唤醒) ┃ __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode) ┃ 1. 处理 Timer ┃ ┃ 2. 处理 GCD Async To Main Queue ┃ ┃ 3. 处理 Source1 <━━┛ ┃ 09. 处理 Blocks ┃ 10. 根据前面的执行结果,决定如何操作 ┗━ 1. 回到 `第2步` 2. 退出 Loop ━━━━┓ 11. 通知 Observers: 退出 Loop <━━┛ --> __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit -
RunLoop调用细节-
__CFRunLoopDoObservers函数内最终会通过调用__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__函数完成通知 -
__CFRunLoopDoBlocks函数内最终会通过调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__函数 处理 blocks -
__CFRunLoopDoTimers函数内最终会通过调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__函数 执行定时器逻辑 -
__CFRunLoopDoSources0函数内最终会通过调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__函数 执行source0逻辑 -
__CFRunLoopDoSource1函数内最终会通过调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__函数 执行source1逻辑 - 通过
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__函数处理GCD Async To Main Queue- GCD 只有这种特殊情况交给 RunLoop 处理,GCD 有自己的处理逻辑,不依赖于 RunLoop
- e.g.
>
>
oc > dispatch_async(dispatch_get_global_queue(0,0), ^{ > // 这里子线程的任务并不是 RunLoop 来处理的 > dispatch_async(dispatch_get_main_queue(), ^{ > // 返回主线程做一些任务,这时是 RunLoop 来处理的 > }); > }); >
-
-
RunLoop 在开发中的实际应用场景
- 控制线程生命周期(线程保活)
- 解决 NSTimer 在滑动时停止工作的问题
- 监控应用卡顿
- 性能优化
-
线程保活在使用时的注意点
- 如果保活的线程并非持续存在,有一定的生命周期
-
通过 NSThread 的 block 方式添加 Source,相对比 target ,不会造成对 target 的一层强持有
-
保证 Thread 的销毁,停止掉保活线程的 RunLoop (
CFRunLoopStop(CFRunLoopGetCurrent())) -
[[NSRunLoop currentRunLoop] run]默认开启一个无限循环,重复调用runMode:beforeDate:,可以根据自己的标识调用runMode:beforeDate:方法-
e.g.
while(weakSelf && !weakSelf.isStop) { [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]; }
-
-
取消 NSThread 的强引用:
self.thread = nil,停止runloop后即便Thread还存在没有被销毁,也不再能继续执行任务,再次添加任务就会 crash,所以在停止RunLoop后及时将 thread 置为 nil, 向线程中添加任务前进行判断 -
为了保证
RunLoop被正常的停止, 并且thread和 self(比如当前控制器)正常的销毁,停止RunLoop函数在执行时performSelector:onThread:withObject:waitUntilDone:最后的参数一定要传YES, 等待stop执行完毕再向下执行, 不要让self销毁过早产生 crash 的同时,在 run 的判断条件中一定要检查self != nil
如果使用
CFRunLoop来启动RunLoop会使控制逻辑更加的简单CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false)(最后一个参数false可以保证执行完一次任务后不退出RunLoop,这样就不需要注意点3中那层while循环和条件判断) -
- 如果保活的线程并非持续存在,有一定的生命周期