概述:
1.概念
2.数据结构
3.事件循环机制
4.RunLoop和NSTimer的关系
5.RunLoop和多线程
1、什么是RunLoop?
RunLoop是通过内部维护的事件循环来对事件/消息进行管理的一个对象。
- 没有消息需要处理时 休眠以避免资源占用;(用户态——>内核态)
- 有消息需要处理时,立刻被唤醒。(内核态——>用户态)
<1.1>.用户态和内核态
- 我们的应用程序一般都是运行在用户态层面上,用户态包含了开发所使用的绝大多数api。
- 当我们发生了系统调用,使用到一些关于操作系统以及一些底层内核相关的指令或者api的话,就触发了系统调用。
<1.2>.为什么main函数不会退出?
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
1>.UIApplicationMain里面开启了一个主线程的Runloop,Runloop可以在有事情做的时候做事,没有事情做的时候等待。通过用户态和内核态的切换,从而避免资源的占用。
2>.Runloop可以接收消息和等待以及处理消息。
3>.等待不等于死循环。
2、Runloop的数据结构
NSRunLoop(Foundation框架)是CFRunLoop(Core Foundation框架)的封装,提供了面向对象的API。
CFRunLoop
CFRunLoopMode
Source/Timer/Observer
<2.1>.CFRunLoop
pthread:线程【一一对应(Runloop和线程)】
currentMode:CFRunLoopMode
modes:NSMutableSet<CFRunLoopMode *>
commonModes:NSMutableSet<NSString *>
commonModeItems:Observer、timer、source的集合
<2.2>.CFRunLoopMode
name:字符串,如NSDefaultRunloopMode
source0
source1
timers:NSMutableArray
observers:NSMutableArray
<2.3>.CFRunloopSource
source0:需要手动唤醒线程。
source1:具备唤醒线程的能力。
<2.4>.CFRunloopTimer
和NSTimer是免费调转换(toll-free bridge)的。
<2.5>.CFRunloopObserver
观测时间点:
1.kCFRunLoopEntry:入口时机,启动的时候
2.kCFRunLoopBeforeTimers:通知观察者将要对timer进行处理了
3.kCFRunLoopBeforeSources:将要处理source事件
4.kCFRunLoopBeforeWaiting:将要进行休眠状态
5.kCFRunLoopAfterWaiting:内核态切换到用户态不久
6.kCFRunLoopExit:退出的通知
<2.6>.Runloop和mode以及内部的source、timer、observer的关系:

<2.7>. Modes说明:
一共有5个 Mode,常用的有仨,其中一个是占位用的。5个 Mode分别是:
<1>.DefaultMode:最常用的模式,适用于大部分情况,常用于处理 UI 事件;
<2>.UITrackingMode:用于在拖动时保持界面更新;
<3>.CommonModes:允许多个模式之间共享事件源和输入源,例如同时支持滚动和触摸事件;CommonMode不是实际存在的一种Mode。是同步Source/Timer/Observer到多个Mode中的一种种技术方案。
<4>.UIInitializationMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用;
<5>.GSEventReceiveMode: 接受系统事件的内部 Mode,通常用不到。
<2.8>.CFRunloopCommonModes
CommonMode不是实际示存在的一种Mode。
是同步Source/Timer/Observer到多个Mode中的一种种技术方案。
3、事件循环的实现机制

<3.1>.app从点击一个图标到启动、运行到最终退出,这个过程中系统都发生了什么?
调用main函数之后,内部会调用UIApplicationMain,启动主线程的runloop,主线程的runloop处于休眠状态,如果点击屏幕,会产生一个mach port,基于mach port会转成source1,把主线程唤醒,执行处理事件。当我们把程序杀死,就会发生一个runloop的退出,发送一个通知即将退出runloop。runloop退出之后,线程销毁掉了。
4、RunLoop和NSTimer的关系
void CFRunLoopAddTimer(runLoop, timer, commonMode)
5、RunLoop和多线程
- 线程是和RunLoop-一对应的。
- 主线程默认就有 Runloop。
- 子线程的 Runloop 以懒加载的形式创建。
- Runloop 存储在一个全局的可变字典里,线程是 key ,Runloop 是 value。
<5.1>.怎样实现一个常驻线程?
- 1.为当前线程开启一个RunLoop。
- 2.向该RunLoop中添加一个Port/ Source等维持RunLoop的事件循环。
- 3.启动该RunLoop。
#import "RLObject.h"
static NSThread *thread = nil;
// 标记是否要继续事件循环
static BOOL runAlways = YES;
@implementation RLObject
+ (NSThread *)threadForDispatch {
if (thread == nil) {
@synchronized (self) {
if (thread == nil) {
// 创建线程
thread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequest) object:nil];
// 启动
[thread start];
}
}
}
return thread;
}
+ (void)runRequest {
// 创建一个source
CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// 创建RunLoop,同时向RunLoop的default模式下添加source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 如果可以运行
while (runAlways) {
@autoreleasepool {
// 令当前的RunLoop运行在DefaultMode下面
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
}
}
// 某一时机,静态变量 runAlways == NO的时候,保证跳出RunLoop,线程退出
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
}
@end