iOS 底层 runloop内部的数据结构 - AlvinSunny/OC-TheUnderlying GitHub Wiki
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
}
RunLoop在同一时段内只能并且必须在一种特定的Mode下run,这个Mode被称为currentMode
如果想更换currentMode,需要暂停当前的loop,然后重启新的loop,这样可以分离开不同的Source/Timer/Observer,互不影响
可以定义自己的mode,但是几乎没有这么做的;
-
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,通常用不到
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name; //模式名
CFMutableSetref _sources0; //CFRunLoopSourceRef,事件处理: 点击 、PerformSelector(触摸事件)
CFMutableSetref _sources1; //基于Prot(端口)的线程间通信,系统事件捕捉
CFMutableArrayRef _observers; //CFRunLoopObserverRef 监听器
CFMutableArrayRef _timers; //CFRunLoopTimeRef 定时器
}
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
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相当于观察者模式的观察者,用来向观察者报告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的地址
// 创建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);



![RunLoop、Mode、[Source、Timer、Observer]之间的关系@2x.png](https://upload-images.jianshu.io/upload_images/4068785-4edd41be198d4e06.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)