RunLoop是通过内部维护的事件循环来对 事件、消息进行管理的一个对象
事件循环(EventLoop)
没有消息需要处理时,休眠以避免资源占用
有消息需要处理时,立刻被唤醒
用户态
我们可以理解为我们日常开发的API接口都是面向我们用户态的。也就是程序员和开发者自行定义和开发的功能。内核态
就是系统或者底层的内核方面的系统调用。
那么为什么main函数不会退出,我们可以看看工程里的main函数
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
其实在main中存在的这个UIApplicationMain
这个函数就是在内部启动了一个RunLoop
从而不断的接收消息和休眠,从而导致main方法不会退出了。
RunLoop对象
OS中有2套API来访问和使用RunLoop
- Foundation:NSRunLoop
- Core Foundation:CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是基于CFRunLoopRef的一层OC包装
CFRunLoopRef是开源的 https://opensource.apple.com/tarballs/CF/ ```objectivec //Foundation [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象 [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
//Core Foundation CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象 CFRunLoopGetMain(); // 获得主线程的RunLoop对象
//NSRunLoop <—> CFRunLoopRef 相互转化 NSLog(@”NSRunLoop <—> CFRunloop == %p—%p”,CFRunLoopGetMain() , [NSRunLoop mainRunLoop].getCFRunLoop);
【打印结果】:内存地址相同
0000-00-13 00:30:16.527 MultiThreading[57703:1217113] NSRunLoop <—> CFRunloop == 0x60000016a680—0x60000016a680
<a name="69fa3701"></a>
## RunLoop的基本作用
1. 保持程序的持续运行(如:程序一启动就会开启一个主线程(中的 runloop 是自动创建并运行),runloop 保证主线程不会被销毁,也就保证了程序的持续运行)。
2. 处理App中的各种事件(如:touches 触摸事件、NSTimer 定时器事件、Selector事件(选择器 performSelector))。
3. 节省CPU资源,提高程序性能(有事情就做事情,没事情就休息 (其资源释放))。
4. 负责渲染屏幕上的所有UI。
附:CFRunLoop.c 源码
```c
#【用DefaultMode启动,具体实现查看 CFRunLoopRunSpecific Line2704】
#【RunLoop的主函数,是一个死循环 dowhile】
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
/*
参数一:CFRunLoopRunSpecific 具体处理runloop的运行情况
参数二:CFRunLoopGetCurrent() 当前runloop对象
参数三:kCFRunLoopDefaultMode runloop的运行模式的名称
参数四:1.0e10 runloop默认的运行时间,即超时为10的九次方
参数五:returnAfterSourceHandled 回调处理
*/
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
//【判断】:如果runloop没有停止 且 没有结束则继续循环,相反侧退出。
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
#【直观表现】
RunLoop 其实内部就是do-while循环,在这个循环内部不断地处理各种任务(`比如Source、Timer、Observer`),
通过判断result的值实现的。所以 可以看成是一个死循环。
如果没有RunLoop,UIApplicationMain 函数执行完毕之后将直接返回,就是说程序一启动然后就结束;
Runloop 开启&退出
验证 Runloop 是在UIApplicationMain 中开启。
# int 类型返回值
UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString * __nullable principalClassName, NSString * __nullable delegateClassName);
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"开始");
int number = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"结束");
return number;
}
}
#【验证结果】:只会打印开始,并不会打印结束。
----
#【Runloop 的退出条件】。
App退出;线程关闭;设置最大时间到期;
【注解】:说明在UIApplicationMain函数内部开启了一个和主线程相关的RunLoop (保证主线程不会被销毁),导致 UIApplicationMain 不会返回,一直在运行中,也就保证了程序的持续运行。
Runloop和线程关系
- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
- 创建子线程RunLoop,通过
[NSRunLoop currentRunLoop]
在子线程内部获取,不获取则不会创建,方法调用时,会先查看字典,有则返回,没有则创建并存入字典中。 - 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
RunLoop
在第一次获取时创建,在线程结束时销毁。
CFRunLoopRef源码
# 1. 主线程相关联的RunLoop创建
// 创建字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建主线程 根据传入的主线程创建主线程对应的RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主线程 将主线程-key和RunLoop-Value保存到字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
# 2. 创建与子线程相关联的RunLoop
// 从字典中获取子线程的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
// 如果子线程的runloop不存在,那么就为该线程创建一个对应的runloop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 把当前子线程和对应的runloop保存到字典中
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
RunLoop的运行逻辑
其内部代码整理如下 :
/// 用DefaultMode启动
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode启动,允许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
/// RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
/// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里没有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 内部函数,进入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 触发 Source0 (非port) 回调。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
/// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
/// 一个基于 port 的Source 的事件。
/// 一个 Timer 到时间了
/// RunLoop 自身的超时时间到了
/// 被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
/// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
/// 收到消息,处理消息。
handle_msg:
/// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 9.2 如果有dispatch到main_queue的block,执行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
/// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 执行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 进入loop时参数说处理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部调用者强制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
/// 如果没超时,mode里没空,loop也没被停止,那继续loop。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
RunLoop休眠的实现原理
从上面代码第7步可以看到,RunLoop 的核心是基于mach port
的,其进入休眠时调用的函数是 mach_msg()
。
/// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
/// 一个基于 port 的Source 的事件。
/// 一个 Timer 到时间了
/// RunLoop 自身的超时时间到了
/// 被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
为了实现消息的发送和接收,mach_msg()
函数实际上是调用了一个 Mach 陷阱 (trap),即函数mach_msg_trap()
,陷阱这个概念在 Mach 中等同于系统调用。当你在用户态调用 mach_msg_trap()
时会触发陷阱机制,切换到内核态;内核态中内核实现的 mach_msg() 函数会完成实际的工作,如下图:
- 例如你在模拟器里跑起一个 iOS 的 App,然后在 App 静止时点击暂停,你会看到主线程调用栈是停留在
mach_msg_trap()
这个地方
应用
运行循环与时钟
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(fire) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)fire {
static int num = 0;
/// 耗时操作
for (int i = 0; i < 1000 * 1000; ++i) {
[NSString stringWithFormat:@"hello - %d", i];
}
NSLog(@"%d", num++);
}
@end
运行测试,会发现卡顿非常严重
- 将时钟添加到其他线程工作
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(startTimer) object:nil];
[self.thread start];
}
- (void)startTimer {
@autoreleasepool {
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(fire) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
_timerRf = CFRunLoopGetCurrent();
CFRunLoopRun();
NSLog(@"come here");
}
}
注意:主线程的运行循环是默认启动的,但是子线程的运行循环是默认不工作的,这样能够保证线程执行完毕后,自动被销毁
- 停止运行循环
- (IBAction)stop {
if (_timerRf == NULL) {
return;
}
CFRunLoopStop(_timerRf);
_timerRf = NULL;
}