RunLoop 初识
2020-04-13 本文已影响0人
游城十代2dai
0x00 RunLoop 是什么?
在程序运行的过程中循环做的一些事情, 保证 main 函数不会直接退出, 并处理各种事件, 节省 CPU 资源, 提高性能
0x01 应用在哪里?
- 定时器, PerformSelector
- 解决 NSTimer 在滑动时停止工作的问题
NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { // handle }]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- GCD, 线程
- 控制线程的生命周期(线程保活)
// 创建 thread 并启用 RunLoop - (void)run { NSLog(@"%s --- START", __func__); [NSRunLoop.currentRunLoop addPort:NSPort.new forMode:NSDefaultRunLoopMode]; // run 是一个不会停止的循环, 永不销毁的RunLoop // [NSRunLoop.currentRunLoop run]; while (self && !self.needThreadStop) { [NSRunLoop.currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture]; } NSLog(@"%s --- END", __func__); } // 停止 RunLoop, 并挂掉线程 - (void)stop { NSLog(@"%s --- %@", __func__, NSThread.currentThread); self.needThreadStop = YES; CFRunLoopStop(CFRunLoopGetCurrent()); self.thread = nil; NSLog(@"%s --- %@", __func__, NSThread.currentThread); } // 点击方法, 触发 stop - (IBAction)threadStop:(id)sender { [self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES]; }
- 事件的响应, 手势的识别, UI 的刷新
- 监控应用卡顿
- 性能优化
- 网络请求
- AutoreleasePool
0x02 RunLoop 对象
有两套 API 可以获取
- NSRunLoop : 基于 CFRunLoopRef 进行包装的 OC 对象,
[NSRunLoop currentRunLoop]
- CFRunLoopRef: 是一套开源的代码
CFRunLoopGetCurrent()
0x03 线程
- 每条线程都有唯一的一个与之对应的 RunLoop 对象(一一对应关系)
- RunLoop 保存在一个全局的 Dic 里面, 线程作为 key, RunLoop 作为 value
- 线程刚创建的时候并没有 RunLoop 对象, RunLoop 对象在第一次获取时创建, 即
[NSRunLoop currentRunLoop]
或CFRunLoopGetCurrent()
- RunLoop 在线程结束时销毁
- 主线程的 RunLoop 是自动获取的, 子线程默认没有开启 RunLoop, 需要手动开启, 比如
dispatch_async
, 使用的时候并没有 RunLoop, 需要在 block 内获取一次才会创建
0x04 RunLoop 的类
-
息息相关的五个类
-
typedef struct __CFRunLoop * CFRunLoopRef;
-
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
-
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
-
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
-
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
-
-
RunLoop 最关心的结构体(简化版本, 原版本去看源码吧):
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
};
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
0x05 CFRunLoopModeRef
- CFRunLoopModeRef 代表 RunLoop 的运行模式
- 一个 RunLoop 有多个模式(结构体中 _modes 体现), 每个模式又包含若干个
Source0/Source1/Observer/Timer
(__CFRunLoopMode 结构体成员体现) - RunLoop 启动时只能选择一种模式, 作为 CurrentMode
- 如果需要切换 Mode, 只有退出当前 RunLoop, 再选择一个 Mode 进入
- 如果 RunLoop 中没有任何
Source0/Source1/Observer/Timer
, 就会立即退出 - 常见的 3 种模式:
- NSDefaultRunLoopMode (kCFRunLoopDefaultMode) 默认的 Mode
- UITrackingRunLoopMode 保证滑动时不受其他 Mode 影响
- NSRunLoopCommonModes (kCFRunLoopCommonModes) 包含前两种, 并不是一种模式, 而是一个标志, 让系统去选择
CFMutableSetRef _commonModes;
0x06 CFRunLoopObserverRef
// 创建
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { });
// 添加监听
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(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
};
0x07 运行逻辑
- Sources0
- 触摸事件
performSelector:onThread:
- Sources1
- 基于 Port 的线程通信
- 系统事件捕捉(捕捉到后给 Source0 处理) (响应用户操作的流程)
- Timers
- NSTimer
performSelector: withObject: afterDelay:
- Observers
- 用于监听 RunLoop 的状态
- UI 刷新(before waiting)
- AutoreleasePool(before waiting)
- 逻辑流程
- 通知 Observers : 进入 Loop (__CFRunLoopDoObservers 所有的通知操作)
- 通知 Observers : 即将处理Timers
- 通知 Observers : 即将处理Sources
- 处理 Blocks (__CFRunLoopDoBlocks)
- 处理Source0 (__CFRunLoopDoSources0) ( 函数返回值为 True 再次处理 Blocks )
- 如果存在 Source1 就跳转到第 8 步, 否则继续
- 通知 Observers : 开始睡眠( 等待唤醒 )
- 通知 Observers : 结束睡眠( 被唤醒 )
- 处理 Timer (__CFRunLoopDoTimers)
- 处理 GCD (CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE)
- 处理 Source1 (__CFRunLoopDoSource1)
- 处理 Blocks
- 根据结果决定如何操作
- 回到第二步继续处理事件
- 退出 Loop (换 Mode)
- 通知 Observers : 退出 Loop
0x08 睡眠原理
- 等待消息 (mach_msg 内核 API)
- 没有消息就睡眠休息
- 有消息就唤醒处理