iOS 底层 - runloop内部的数据结构
本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢 !
RunLoop
相关的C语言类:
CFRunLoopRef
CFRunLoopModeRef
--> 运行模式
CFRunLoopSourceRef
-- >事件处理: 点击 、PerformSelector(触摸事件)
CFRunLoopTimeRef
--> 定时器
CFRunLoopObserverRef
--> 监听器
typedef struct __CFRunLoop *CFRunLoopRef;
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes; //通用Mode集合
CFMutableSetRef _commonModeItems; //存放着在通用模式下工作的timer或其他
CFRunLoopModeRef _currentMode; //当前运行模式
CFMutableSetRef _modes; //内部存储着若干个CFRunLoopModeRef
}
CFRunLoopMode
RunLoop
在同一时段内只能并且必须在一种特定的Mode
下run
,这个Mode被称为currentMode
如果想更换currentMode
,需要暂停当前的loop
,然后重启新的loop
,这样可以分离开不同的Source/Timer/Observer
,互不影响
可以定义自己的mode,但是几乎没有这么做的;
关于CFRunLoopModeRef
-
CFRunLoopModeRef
代表RunLoop
的运行模式 -
一个RunLoop包含若干个Mode,每个Mode又包含若干个
Source0/Source1/Timer/Observer
-
RunLoop启动时只能选择其中一个Mode作为
currentMode
-
如果需要切换Mode,只能退出当前的loop(但并不会退出程序,不要混淆),再重新选择一个Mode进入;
不同组的Source0/Source1/Timer/Observer能分割开来,互不影响
-
如果Mode里
没有任何Source0/Source1/Timer/Observer
,RunLoop会立刻退出
-
常见的几种Mode:
-
kCFRunLoopDefaultMode
==NSDefaultRunLoopMode
: App的默认Mode,通常主线程是在这个Mode下运行 -
UITrackingRunLoopMode
: 界面跟踪Mode,用于ScrollView
追踪触摸滑动,保证界面滑动时不受其他Mode影响
-kCFRunLoopCommonModes
:默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode
;
kCFRunLoopCommonModes
并不是一个真的模式,它只是一个标记,如:被标记的Timer
可以在kCFRunLoopDefaultMode模式和UITrackingRunLoopMode模式
下运行。
-
-
基本用不到的Mode:
-
UIInitializationRunLoopMode
:私有的mode,App启动的时候的状态,加载出第一个页面后,就转成了Default -
GSEventReceiveRunLoopMode
接受系统事件的内部 Mode,通常用不到
-
CFRunLoopMode中各成员的作用
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name; //模式名
CFMutableSetref _sources0; //CFRunLoopSourceRef,事件处理: 点击 、PerformSelector(触摸事件)
CFMutableSetref _sources1; //基于Prot(端口)的线程间通信,系统时间捕捉
CFMutableArrayRef _observers; //CFRunLoopObserverRef 监听器
CFMutableArrayRef _timers; //CFRunLoopTimeRef 定时器
}
CFRunLoopSource
CFRunLoopSource
是RunLoop的数据源抽象类,类似Objective-C中的协议protocol,实现了这个protocol就可以充当RunLoop的数据源(几乎没有这么做的),RunLoop自己定义了两个Source:Source0
和Source1
- Source0: 处理App内部事件; 比如:
- 屏幕响应UIEvent, CFSocket(套接字),我们点击屏幕(touchesBegan:withEvent:)就是Source0事件
- 开发者主动
实现的线程间通信:performSelector:onThread:
aSelector: 方法名
onThread: 需要访问的线程
withObject: 实现SEL方法时传递的参数
waitUntilDone:
- 设置为YES: 表示需要等当前方法选择器中的方法执行完再执行下面的代码,防止还没停止当前实例对象就先释放掉了。
- 设置为NO:表示不需要等当前方法选择器中的方法执行完,可以继续执行下面的代码
modes: 一个字符串数组,它标识允许在其中执行指定选择器的模式。此数组必须包含至少一个字符串。如果为该参数指定nil或空数组,则此方法将在不执行指定的选择器的情况下返回。
//给某个线程发送消息
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array
//给主线程发消息
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array
- Source1: 由内核管理,比如: 基于mach_port端口的线程或进程间通信、系统事件捕捉
事件是通过Source1来捕捉,然后再分发到Source0来处理的
注意:mach_port
是iOS系统中进程间通信的一种方式,如果进程1往一个port中发送一个消息,此时进程2监听了这个port,就会拿到这个消息
NSPort是对CoreFoundation中的CFMachPort
和CFMessagePort
的封装
来看一下对source事件的定义

CFRunLoopSource中用union确保这个source要么是source0,要么是source1
看一下source0和source1的具体定义:


souce0中定义的都是函数指针
source1中除了函数指针,还有一个mach_port
Timers
NSTimer
performSelector:withObject:afterDelay:
- 该方法是拿到RunLoop并且把Timer添加到RunLoop里面,
但不会启动RunLoop
;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
CFRunLoopObserver
CFRunLoopObserver
相当于观察者模式的观察者,用来向观察者报告RunLoop当前的状态
- 用于监听RunLoop的状态
- UI刷新(
BeforeWaiting
) -
Autorelease pool
(BeforeWaiting)自动释放池
AutoreleasePool
在RunLoop的两次sleep之间对AutoreleasePool进行pop和push,将这次loop中产生的Autorelease对象进行释放
下面是RunLoop中定义的状态:
/* 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 // 所有状态
};
看以下代码:
self.view.backgroundColor = [UIColor redColor];
view的背景颜色什么时候被修改为红色 ?
执行到到这行代码时,会先把代码保存下来;Observers(监听器)监听到将要休眠之前会刷新UI,在这时候改变了背景颜色为红色。

疑问:以下输出结果相同吗?
NSLog(@"%p %p", [NSRunLoop currentRunLoop], [NSRunLoop mainRunLoop]);
NSLog(@"%p %p", CFRunLoopGetCurrent(), CFRunLoopGetMain());
肯定不相同,因为NSRunLoop是对CFRunLoop的封装,会把CFRunLoopGetCurrent()的地址封装在NSRunLoop的地址中;可以通过输出对象NSLog(@"%@", [NSRunLoop mainRunLoop]);看出
NSLog(@"NSRunLoop地址 %p %p", [NSRunLoop currentRunLoop], [NSRunLoop mainRunLoop]);
NSLog(@"CFRunLoop地址 %p %p", CFRunLoopGetCurrent(), CFRunLoopGetMain());
NSLog(@"NSRunLoop 对象 %@", [NSRunLoop mainRunLoop]);
输出结果:
NSRunLoop
地址 0x6000018e1fe0 0x6000018e1fe0
CFRunLoop
地址 0x6000000e4200 0x6000000e4200
NSRunLoop
对象 <CFRunLoop 0x6000000e4200
[0x110d44ae8]>
很明显可以看出:NSRunLoop的结构中存储着0x6000000e4200,0x6000000e4200就是CFRunLoop的地址;
-
0x6000000e4200
是真正的runloop -
0x6000018e1fe0
代表的是OC对象NSRunLoop的地址
RunLoop监听状态监听逻辑
// 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
//监听到状态的block回调
switch (activity) {
case kCFRunLoopEntry: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopExit - %@", mode);
CFRelease(mode);
break;
}
default:
break;
}
});
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);