主要讲解Runloop的底层结构;
文中使用的 CF源码是CF-1153.18版本;
Runloop 简析
Runloop 补充
1. Runloop 的应用范畴
- 定时器(
timer
)和PerformSelector
; - GCD 的 Asyn Main Queue;
- 事件相应, 手势识别, 界面刷新 ;
- 网络请求;
- AutoreleasePool
2. Runloop 的底层结构
我的都知道Runloop
有 CFRunloopRef
和基于它封装的CFRunloop
,所以我们只需要探究下CFRunloopRef
的结构即可得知它的底层结构;
///源码中CFRunLoopRef的定义如下
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
===>
///__CFRunLoop的定义如下, 有很多成员, 不过我们只需要确定它的主要组成是各个 Mod 即可;
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
///每一个 runloop 中都有一个线程, 确保线程和 runloop 是一一对应的;
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
///Mod的集合
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
===>
///Mod的底层结构如下
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
///集合sources0
CFMutableSetRef _sources0;
///集合souces1
CFMutableSetRef _sources1;
///数组observers
CFMutableArrayRef _observers;
///数组timers
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
...
}
关于Runloop
中常用的几个类如下:
CFRunLoopRef
, CFRunLoopModeRef
, CFRunLoopSourceRef
, CFRunLoopObserverRef
, CFRunLoopTimerRef
;
在CFRunLoopModeRef
中的几个主要变量的含义:
-
source0
: 处理触摸事件,或者performSelector:OnThread:
方法; -
source1
: 基于port
的线程通讯(addPort: forMode:
), 系统事件捕捉(例如点击屏幕后首先是source1
捕捉然后再封装转发给source0
处理); -
Timer
: 就是NSTimer
, 调用performSelector: withObject: afterDelay:
也是类似操作; -
Observer
:监听Runloop的状态; 主线程UI刷新(例如设置一个颜色, 代码执行完后并不会立即改变, 而是在Runloop
状态BeforeWaiting
之前刷新的);另外AutoreleasePool
也是一样具体什么时候release
是Runloop
的监听者决定;
如果
Mode
中没有任何的Source0
,Source
,Timer
,Observer
则Runloop
会立即退出;
3. Runloop的几种状态:
我们都知道Runloop
是一个事件循环, 整个循环有多少种状态呢, 源码种定义如下:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
///即将进入 Runloop
kCFRunLoopEntry = (1UL << 0),
///即将处理 Timer
kCFRunLoopBeforeTimers = (1UL << 1),
///即将处理 Source
kCFRunLoopBeforeSources = (1UL << 2),
///即将进入休眠
kCFRunLoopBeforeWaiting = (1UL << 5),
///即将唤醒 Runloop
kCFRunLoopAfterWaiting = (1UL << 6),
///即将退出 Runloop
kCFRunLoopExit = (1UL << 7),
///所有状态集合
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
-
kCFRunLoopEntry
: 即将进入Runloop
; -
kCFRunLoopBeforeTimers
: 即将处理Timer
; -
kCFRunLoopBeforeSources
: 即将处理Sources
; -
kCFRunLoopBeforeWaiting
: 即将进入休眠(事情已经处理完毕); -
kCFRunLoopAfterWaiting
: 从休眠中唤醒; -
kCFRunLoopExit
: 即将退出Runloop
; -
kCFRunLoopAllActivities
: 所有状态集合;
关于这些状态的测试, 具体详见补充1;
4. Runloop在日常开发种的使用场景
具体测试代码相见下方补充阶段;
- 控制线程的生命周期(线程保活);补充2
- 解决
NSTimer
在滑动时失效的问题;补充3 - 监控应用卡顿;补充4
- 性能优化;补充5
补充
补充1:Runloop的各个状态测试
测试代码如下:
///创建一个Observer
CFRunLoopObserverRef CFObserver = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry: {
NSLog(@"Runloop即将进入Runloop: kCFRunLoopEntry");
break;
}
case kCFRunLoopBeforeTimers: {
NSLog(@"Runloop即将处理Timer: kCFRunLoopBeforeTimers");
break;
}
case kCFRunLoopBeforeSources: {
NSLog(@"Runloop即将处理Source: kCFRunLoopBeforeSources");
break;
}
case kCFRunLoopBeforeWaiting: {
NSLog(@"Runloop即将进入休眠: kCFRunLoopBeforeWaiting");
break;
}
case kCFRunLoopAfterWaiting: {
NSLog(@"Runloop从休眠中唤醒: kCFRunLoopAfterWaiting");
break;
}
case kCFRunLoopExit: {
NSLog(@"Runloop即将退出: kCFRunLoopExit");
}
default:
break;
}
});
///为Runloop添加Observer
CFRunLoopAddObserver(CFRunLoopGetMain(), CFObserver, kCFRunLoopCommonModes);
///CF框架下Create或者Copy的对象要进行释放
CFRelease(CFObserver);
具体打印结果由于篇幅太大, 不再贴出, 请自行下载测试代码查看打印结果;
补充2:控制线程的生命周期
通过启动子线程的Runloop
并添加source1
达到线程不会 kill
的效果; 详解请看这篇文章
/*
case3:
子线程中开启runloop, 为其添加任意的souce0/souce1/timer/observer, 并让其run, 则此线程由于runloop的关系就不会被销毁;
*/
self.thread3 = [[XThread alloc] initWithBlock:^{
/*
我们知道runloop中如果没有任何source0/souce1/timer/observer 则runloop会立即退出;
所以为runloop添加source1, 然后让runloop执行run;
*/
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init ] forMode:NSDefaultRunLoopMode];
/*
注意run的释义:If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
大致翻译: 如果没有souces或者timers添加到runloop中则方法理解退出; 如果有,将在NSDefaultRunLoopMode模式下无限次执行runMode:beforeDate:来处理添加的souces和timers;
注意: 调用 runloop 的 run 方法则不再能取消 runloop 即使是调用了CFRunLoopStop(CFRunLoopGetCurrent()); 也只是停了其中一次循环;
*/
[[NSRunLoop currentRunLoop] run];
}];
[self.thread3 start];
另一种方式
///创建一个source -- 往 runloop 中添加 source1 source0 timer observer 均可打到效果
///初始化 context
CFRunLoopSourceContext context = {0};
///当一个runloop中没有事件源处理时, 运行完就会退出;
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
///1. 2. 创建runloop 同时向runloop中的defaultMode下面添加source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
///3. 启动runloop
while (shouldRun) {
@autoreleasepool {
///令当前的runloop运行在defaultMode下
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e20, true);
}
}
///某个时机, 将静态变量shouldRun = NO时, 退出runloop, 进而退出线程;
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
补充3: 解决NSTimer在界面拖动时暂时失效的问题
正常情况下, NSTimer
都是添加在DefaultMode
模式下; 一旦界面拖动时Runloop
就会切换模式到TrackingMode
模式下, NSTimer
就会暂时失效;
解决方案:将NStimer
添加到Runloop
的CommonMode
模式下;这样Runloop就会同时处理DefaultMode
和TrackingMode
中的事件;
static NSInteger i = 1;
NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"执行次数: %ld", i ++);
}];
[timer fire];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
补充4:详见性能优化部分
补充5:相见性能优化部分
文中测试代码
参考文章和下载链接
Apple 一些源码的下载地址