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处理
- 基于
-
Timers
NSTimer
performSelector: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循环
和条件判断
) -
- 如果保活的线程并非持续存在,有一定的生命周期