RunLoop是iOS/macOS下的事件循环机制,同时也是一个OC对象,该对象管理了其需要处理的时间和消息,并提供了一个入口函数来执行。
Runloop的代码逻辑如下:
1 | function loop() { |
RunLoop的基本作用:
- 保持程序的持续运行
- 处理App中的各种事件(比如触摸事件、定时器事件等)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
- ……
以下都属于Runloop的应用范畴:
- 定时器(Timer)、方法调用(PerformSelector)
- GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
- 自动释放池 AutoreleasePool
Runloop对象
iOS中有2套API来访问和使用RunLoop:
- Foundation:NSRunLoop
- Core Foundation:CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象,NSRunLoop是基于CFRunLoopRef的一层OC包装。CFRunLoopRef是开源的:https://opensource.apple.com/tarballs/CF/。
1 | //获取当前线程的runloop |
RunLoop与线程
Runloop与线程有如下关系:
- 每条线程都有唯一的一个与之对应的RunLoop对象;
- RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value;
- 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建;
- RunLoop会在线程结束时销毁;
- 主线程会在UIApplicationMain方法中获取Runloop,子线程默认没有开启RunLoop;
在这里需要注意的是,performSelector:withObject:afterDelay:
方法的的本质是往Runloop中添加定时器,而子线程默认没有启动Runloop,所以在子线程中调用该方法不会得到正确的响应。
RunLoop与线程的相关源码如下:
1 | // 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef |
Runloop相关的类
Core Foundation中关于RunLoop的5个类:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
1 | typedef struct __CFRunLoop * CFRunLoopRef; |
CFRunLoopModeRef
CFRunLoopModeRef代表RunLoop的运行模式,一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer。
RunLoop启动时只能选择其中一个Mode,作为currentMode,如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入,这样保证了不同组的Model(Source0/Source1/Timer/Observer)能分隔开来,互不影响。
如果当前Runloop的Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出。
常见的2种Mode:
- kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行;
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响;
另外还有个概念叫CommonModes:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。
主线程的RunLoop中预置的两个Mode:kCFRunLoopDefaultMode和 UITrackingRunLoopMode都已经被标记为”Common”属性。
CFRunLoopSourceRef
CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。
Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程
主要用于:
- Source0
- 触摸事件处理
- performSelector:onThread:
- Source1
- 基于Port的线程间通信
- 系统事件捕捉
屏幕交互事件通过Source1捕捉,然后分发到Source0处理。
CFRunLoopTimerRef
CFRunLoopTimerRef是基于时间的触发器,其包含一个时间长度和一个回调(函数指针)。当其加入到RunLoop时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。主要应用有:
- NSTimer
- performSelector:withObject:afterDelay:
CFRunLoopObserverRef
CFRunLoopObserverRef是Runloop的观察者,每个Observer都包含了一个回调(函数指针),当RunLoop的状态发生变化时,观察者就能通过回调接受到这个变化。主要用于:
- 用于监听RunLoop的状态
- UI刷新(BeforeWaiting)
- Autorelease pool(BeforeWaiting)
一个RunLoop有如下几种状态:
1 | typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { |
给Runloop添加Observer的代码示例如下:
1 | // 创建Observer |
Runloop的运行逻辑
RunLoop 内部的逻辑大致如下:
其代码逻辑整理如下:
1 | /// 用DefaultMode启动 |
RunLoop在实际开中的应用
- 控制线程生命周期(线程保活)
- 解决NSTimer在滑动时停止工作的问题
- 监控应用卡顿
- 性能优化