本文为L_Ares个人写作,以任何形式转载请表明原文出处。
上一节已经探索完了RunLoop
的结构,那么本节将带着上一节探索得到的内容,找个入口,进入RunLoop
逻辑的探索。
首先,根据RunLoop
的基本概念,一个RunLoop
和一个线程一一对应,而我们的程序进程至少拥有一条主线程,那么就证明 : 一个应用程序,它至少拥有一个RunLoop
,就是主线程的RunLoop
,那么本节就从这里为入口,找到主线程RunLoop
的处理事件的逻辑。
准备 : 随意创建一个
iOS
的Project
--->App
。
准备 : CoreFoudation源码
一、 主程序RunLoop的介入时机
对于项目工程,大家都了解,程序的入口处是main()
函数。现在来重新审视一下这个main()
函数到底有什么 :
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
//一个AppDelegate类名变量
NSString * appDelegateClassName;
//一个自动释放池
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
//返回了一个方法调用
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
程序启动正常逻辑应该是进入了这里。
iOS
程序是单进程,进程处理任务需要有线程,线程需要RunLoop
来维持生存,这三个点是必不可少的。
那么从main
里面,唯一有可能实现这些"必不可少"的条件的函数,只有那个被返回的UIApplicationMain
方法。
我们走到ViewController
里面的viewDidLoad
方法里面,给[super viewDidLoad];
来个断点,看看"啥也没有"的App
启动起来都调用了什么。
操作 :
lldb
输入bt
,查看程序的调用堆栈,也可以直接看xcode
的Debug Navigator
。
结果 :
结论 :
主程序RunLoop的调用时机 :
- dyld链接动态库
- main()函数启动
- 调用
Graphics Services
库。这个库大概是处理一个硬件输入的,比如说点击事件之类的。- 开始进入
RunLoop
。
二、主线程RunLoop的入口
根据图1.0.0
所示,可以找到主线程的RunLoop
入口是CoreFoundation
框架下的CFRunLoopRunSpecific
函数。打开CoreFoundation
源码,查找CFRunLoopRunSpecific
。
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
//一个do{xxx}while(0),检查是不是多进程的状态,多进程不能用这个CoreFoundation,好吧,这个无所谓,就一个检查
CHECK_FOR_FORK();
//判断如果现在是正在dealloc一个RunLoop对象的话,证明RunLoop做完事情了,直接返回RunLoop已经执行完毕了。
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
//给RunLoop对象加锁
__CFRunLoopLock(rl);
//根据RunLoop对象和mode的名字去查找当前的mode是哪一个,获取当前RunLoopMode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
//如果RunLoop对象中的currentMode不存在,或者,mode集合中没有当前的mode,直接返回执行完成,RunLoop休眠
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
//压栈。这里就是为了切换RunLoopMode而做的了,切换mode的时候,RunLoop是休眠状态,不是停止!!!
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
//取到上一次RunLoop运行的模式,先存着
CFRunLoopModeRef previousMode = rl->_currentMode;
//设置当前的RunLoop的运行模式
rl->_currentMode = currentMode;
//默认定义RunLoop的运行结果是运行完毕的
int32_t result = kCFRunLoopRunFinished;
//判断RunLoop在当前mode下是即将进入的状态。通知观察者 : RunLoop即将进入
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//执行__CFRunLoopRun函数,获取结果
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//判断RunLoop在当前mode下是即将退出状态。通知观察者 : RunLoop即将退出
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
//给当前的mode开锁
__CFRunLoopModeUnlock(currentMode);
//出栈
__CFRunLoopPopPerRunData(rl, previousPerRun);
//把RunLoop的mode还原到本次执行事件之前的样子
rl->_currentMode = previousMode;
//解锁RunLoop对象
__CFRunLoopUnlock(rl);
return result;
}
每一句都加了注释了,想看的可以参考一下。但是这里面还是要剃出来重点。
到这里了,我探索的就是主线程RunLoop启动了要做什么?
所以,找RunLoop
实际做的事情。
前面的代码很容易看出来,全都根据CFRunLoopMode
的状态做一些处理的。再根据堆栈信息里面再对比一下,不难发现,CFRunLoopRunSpecific
堆栈信息之后,就是__CFRunLoopRun
。
所以,重点就是那个__CFRunLoopRun
函数。
也就是 :
//判断RunLoop在当前mode下是即将进入的状态。通知观察者 : RunLoop即将进入
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//执行__CFRunLoopRun函数,获取结果
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//判断RunLoop在当前mode下是即将退出状态。通知观察者 : RunLoop即将退出
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
结论 :
主程序
RunLoop
在CFRunLoopRunSpecific
中先处理mode
,当mode
符合条件后,通知observer
观察者即将进入RunLoop
,然后执行__CFRunLoopRun
,执行完成后,通知观察者即将退出RunLoop
。
三、主线程RunLoop主体循环的逻辑
接着上面的思路,现在知道了__CFRunLoopRun
是处理RunLoop
事务的函数,那就看一下__CFRunLoopRun
的源码实现。
但是这个源码真的超级长,几百行,全放文章里大家看起来不方便,我就拿其中一些直接触碰RunLoop
逻辑的代码出来。
想看全部的小伙伴可以直接用文章顶部的准备条件跳转苹果官方,下载Core Foundation
的源码,找CFRunLoop.c
查看。
1. 先看一下总体结构 :
/**
* __CFRunLoopRun
*
* @param rl CFRunLoopRef : 运行的 RunLoop 对象
* @param rlm CFRunLoopModeRef : 运行的 mode
* @param seconds CFTimeInterval : RunLoop 超时时间
* @param stopAfterHandle Boolean类型 : (1)true: RunLoop 处理完事件就退出
* (2)false:一直运行直到超时或者被手动终止
* @param previousMode 上一次运行的 mode
*
* @return 返回 4 种状态
*/
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm,
CFTimeInterval seconds, Boolean stopAfterHandle,
CFRunLoopModeRef previousMode)
{
/**
* 判断当前RunLoop或者它的RunLoopMode是不是休眠状态
* 如果是休眠状态,直接返回RunLoop正在休眠,不再做其他的事情
**/
if (__CFRunLoopIsStopped(rl))
{
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped)
{
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
... ... //省略中间的步骤,主要看结构
//定义一个记录RunLoop状态的变量,最后的返回值就是它
int32_t retVal = 0;
do {
/**这里就是RunLoop循环的核心逻辑**/
... ...
}while (0 == retVal); //这里可以看出来,只要retVal == 0,就会一直循环
/*这里会对定义的定时器和结构体做释放*/
... ...
return retVal;
}
通过整体结构不难发现 :
RunLoop
真正处理事件的核心逻辑是一个do{...}while(...)
循环。
2. RunLoop循环逻辑
那么接着看do{...}while{...}
里面都做了什么。内容很多,还有很多宏判断,所以还是挑重要的说。
//先定义了一个布尔变量
Boolean didDispatchPortLastTime = true;
//定义一个记录RunLoop状态的变量,最后的返回值就是它
int32_t retVal = 0;
do {
//1. 设置RunLoop为可唤醒状态
__CFRunLoopUnsetIgnoreWakeUps(rl);
//2. 通知observer观察者,即将触发timer的回调
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//3. 通知observer观察者,即将触发source回调
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//4. 执行加入当前RunLoop的Block
__CFRunLoopDoBlocks(rl, rlm);
/**5. 处理source0的事件
* true : source0有事件
* false : source0没有事件
**/
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
//判断source0有事件要处理
if (sourceHandledThisLoop) {
//执行在前面处理source0过程中,又追加到RunLoop的block事件。
__CFRunLoopDoBlocks(rl, rlm);
}
/**
* 如果source0有事件要处理,或者超时时间设置的是0,则poll是true
* 如果source0没有事件要处理,并且设置的超时时间不是0,poll是false
**/
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//第一次进入do while是不会走进if的
//因为didDispatchPortLastTime上面定义了是true,并且到这里还没改变过
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
//接收dispatchPort这个端口的事件(source1事件)
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
//第一次反正进不来了,后面循环如果进来了,发现有消息事件要处理
//直接跳到handle_msg去处理消息事件。
goto handle_msg;
}
}
//6. 没有事件要处理,通知observer观察者,即将进入休眠状态
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//设置RunLoop为休眠状态
__CFRunLoopSetSleeping(rl);
//7. 这里放一个内循环,用来接收等待端口的消息,只有接到新的消息,才会break,跳出循环向下执行
do {
... ...
//上面接收dispatch端口消息的时候见到过吧,说过了是为了接收消息的。
//它除了接收消息之外,还会睡眠我们的线程。睡眠的时间就取决于参数里面的那个poll是0还是TIMEOUT_INFINITY
//就是说我在poll的时间限制内,还是没有读取到消息,它就会睡眠当前的线程,如果是0就不会睡眠当前的线程。
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
... ...
} while (1);
//8. 唤醒RunLoop
__CFRunLoopUnsetSleeping(rl);
//9. 通知观察者,RunLoop被唤醒了
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//10. 处理接收到的消息(这就是上面那个if跳到的地方)
handle_msg:;
//11. 判断唤醒RunLoop的事件类型,处理唤醒RunLoop的事件
if (Timer)
{
//用户自定义的定时器唤醒的,处理完Timer事件的回调之后,重启RunLoop
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
}
else if (livePort == dispatchPort)
{
//GCD主线程唤醒的
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
else
{
//被输入源唤醒的
//先去从端口找到输入源
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
//判断输入源存在,证明真发消息过来了
if (rls) {
//处理source1输入源的事件
__CFRunLoopDoSource1(rl, rlm, rls)
}
}
//12. 处理RunLoop中的回调
__CFRunLoopDoBlocks(rl, rlm);
//13. 设置RunLoop返回值
if (sourceHandledThisLoop && stopAfterHandle) {
//进入RunLoop时就带有的事件回调,处理好了就返回
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
//RunLoop超时 (比传入参数的超时时间还长)
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
//RunLoop被外界强行手动终止
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
//RunLoopMode被终止
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
//RunLoopMode中就没有 source/timer 要处理的事件
retVal = kCFRunLoopRunFinished;
}
}while(0 == retVal)
总结 :
直接上图吧 :
来个文字的 :
RunLoop处理事件的逻辑
- 发送通知,通知观察者(
observer
),RunLoop即将启动
。- 唤醒RunLoop。
- 发送通知,通知观察者(
observer
),即将处理Timer
的事件。- 发送通知,通知观察者(
observer
),即将处理Source
的事件。- 执行所有输入源的
Block回调
。- 立即处理
source0
的事件。如果在主线程的情况下,判断是否存在source1
的事件。如果存在,则进入handlemag:
函数,放在下面的步骤中处理。- 发送通知,通知观察者(
observer
),RunLoop即将休眠
。- RunLoop休眠,等待事件来将其唤醒,可以唤醒RunLoop的事件如下 :
(1). 某个事件到达了基于端口的源。
(2). 触发了定时器。
(3). 到达RunLoop设置的超时时间点。
(4). RunLoop被手动唤醒。- 接收到了新消息,RunLoop唤醒。
- 发送通知,通知观察者(
observer
),RunLoop刚刚被唤醒。- 处理未被处理的事件 :
(1). 如果是用户自定义的Timer
事件,先处理定时器事件,然后重启RunLoop,重新进入步骤3
。
(2). 如果RunLoop被唤醒了,但是还没有超时,重启RunLoop,重新进入步骤3
。这里注意,这还有一个判断条件是livePort == dispatchPort
的情况,所以只有我们的主线程上的任务才依赖RunLoop的唤醒,而对于子线程的线程调度,还是由GCD来调度。
(3). 如果是source1
唤醒的,直接处理source1
。- 根据处理事件的类型,以及处理结果,设置RunLoop的返回值,只要返回值还是0,则继续循环,如果返回值不是0,则跳出循环。
- 销毁定时器和上下文。
- 退出RunLoop。
- 发送通知,通知观察者(
observer
),RunLoop即将退出。
四、RunLoop循环中的重要函数
可以从上面的逻辑中看到,RunLoop
本身是一个循环,在处理完第一波的事件之后,RunLoop
会进入休眠,在休眠状态下,还会维持一个内部的do {...} while(1)
循环,用以循环接收新的可以唤醒它的消息。
那么,这个内部的do {...} while(1)
循环就是RunLoop
在休眠状态下,接收新消息的入口,所以看一下这里面的重要函数,接收指定内核端口的消息,并且可以睡眠当前线程
的函数——__CFRunLoopServiceMachPort
。
__CFRunLoopServiceMachPort
看下源码 :
/**
* 接收指定内核端口的消息
* @param port : 接收消息的端口的名字
* @param buffer : 消息缓冲区
* @param buffer_size : 缓冲区大小
* @param livePort : 活跃的端口吧。
* @param timeout : 超时时间,单位是ms,如果超时,RunLoop会进入休眠状态
*
* @return : 如果接收到消息,返回true。其他情况,返回false
**/
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
//初始化的缓冲区默认设置成存在
Boolean originalBuffer = true;
//一个typedef的int类型,默认是0
kern_return_t ret = KERN_SUCCESS;
//循环,看看RunLoop的休眠过程中,会发生什么。
//官方注释写的很文艺啊。。。
for (;;) { /* In that sleep of death what nightmares may come ... */
//拿到指向缓冲区的指针的指针
mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
//初始化缓冲区指针的指针的信息
msg->msgh_bits = 0; //看着像数据大小
msg->msgh_local_port = port; //接收消息的端口
msg->msgh_remote_port = MACH_PORT_NULL; //远程端口
msg->msgh_size = buffer_size; //缓冲区大小
msg->msgh_id = 0; //身份标识
//判断是否超时
//如果超时了,RunLoop就可以休眠了
//如果没超时,进入一个do{...}while()循环
if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
//这句就是内核接收消息的核心
//发送或者接收的消息都是指针类型
//如果频繁的直接发送或者接收消息体,会频繁的进行内存复制,消耗性能
//所以XNU选用了单一内核的方式解决问题
//所有的内核组件都共享同一块地址空间,因此传递消息的时候,就只需要传递消息的指针
ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
// Take care of all voucher-related work right after mach_msg.
// If we don't release the previous voucher we're going to leak it.
//mach_msg之后,立即处理所有和凭证关联的工作
//如果不释放之前的凭证,那么就会造成内存泄漏
voucher_mach_msg_revert(*voucherState);
// Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one.
//有人将负责调用voucher_mach_msg_revert。
//这个调用会使接收到的凭证变成当前的凭证
*voucherState = voucher_mach_msg_adopt(msg);
//调用者会要求提供一份凭证的复印件
//在mach_msg之后立即执行这步操作可以确保mach_msg和复印这份凭证中间不产生其他操作
//就是为了保证这份复制凭证的正确性
if (voucherCopy) {
if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
// Caller requested a copy of the voucher at this point. By doing this right next to mach_msg we make sure that no voucher has been set in between the return of mach_msg and the use of the voucher copy.
// CFMachPortBoost uses the voucher to drop importance explicitly. However, we want to make sure we only drop importance for a new voucher (not unchanged), so we only set the TSD when the voucher is not state_unchanged.
*voucherCopy = voucher_copy();
} else {
*voucherCopy = NULL;
}
}
//唤醒RunLoop
CFRUNLOOP_WAKEUP(ret);
//接收/发送消息成功,设置livePort = msgh_local_port
if (MACH_MSG_SUCCESS == ret) {
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
//接收/发送消息超时,设置livePort = MACH_PORT_NULL,释放缓冲区
if (MACH_RCV_TIMED_OUT == ret) {
if (!originalBuffer) free(msg);
*buffer = NULL;
*livePort = MACH_PORT_NULL;
return false;
}
//如果接收/发送的消息体量大,缓冲区没有这么大,break出去,也就是只返回了消息头
if (MACH_RCV_TOO_LARGE != ret) break;
//扩容缓冲区
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
//清空缓冲区
if (originalBuffer) *buffer = NULL;
originalBuffer = false;
*buffer = realloc(*buffer, buffer_size);
}
HALT;
return false;
}
都写了注释了,根据官方文档的描述,加上自己的理解写的,不敢保证都对,注释仅供参考。
在这段代码中,可以知道的是 :
mach_msg
是用来将线程的控制权在内核态
和用户态
之间转换的。
- 没有消息需要处理的情况下,RunLoop会进入休眠,通过
mach_msg
从用户态切换到内核态,接收消息。- 当接收到消息的时候,则立即唤醒RunLoop,调用
mach_msg
回到用户态,处理消息。- 也就是这样,RunLoop就不仅仅是一个
do{...}while()
,因为它在休眠的时候,线程不做任何事情,CPU
是不会分配资源的。
五、关于source0和source1
还是要单独给他们开一个标题。
经过上面的RunLoop的核心逻辑的探索,其实我们可以发现一点,source0
和source1
虽然都是事件源,但是处理的内容是不同的,而且具有的功能也是不同的。
经过(四)中的介绍,很明显的知道了,__CFRunLoopServiceMachPort
可以休眠RunLoop,它控制着线程的控制权在用户态和内核态之间切换,而可以唤醒RunLoop的则是goto handlemsg:
,那么根据处理事物的情况,可以发现source1
才是唤醒的关键。
我们来做个实验。做这个验证需要知道下面几个RunLoop处理的事件在Core Foudation
框架中的定义变量都是什么 :
- Observer :
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline));
- Block :
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__() __attribute__((noinline));
- GCD主队列 :
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() __attribute__((noinline));
- Timer :
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline));
- source0 :
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() __attribute__((noinline));
- source1 :
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__() __attribute__((noinline));
我们就拿touchBegin
来做实验,看看触摸事件是怎么使用到source0
和source1
经过RunLoop处理回调给我们的 :
- 下一个
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
的符号断点。- 在ViewController中实现
touchBegin
的方法。随便写个东西就行。
看结果 :
然后,我们再给source0
下断点。
- 不要停止项目,直接下一个
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
的符号断点。- 然后把断点跳到这里。
看结果 :
得出结论 :
source0
:
- 不具备唤醒RunLoop的能力。
- 接收source1传来的事件,分发给App处理。
source1
:
- 具备唤醒RunLoop的能力。
- 利用捕获线程捕获事件,将事件传递给主线程RunLoop中的source0。
六、RunLoop核心逻辑总结
- RunLoop本身维持着一个不普通的do{...}while()循环,线程执行任务就在这个循环里面执行,直到超时或者外界强行手动退出RunLoop。
- RunLoop可以接收到消息被唤醒是因为
mach_msg
的存在,如果mach_msg
发现消息发送过来,则会唤醒RunLoop,让RunLoop中的线程处理事件,如果mach_msg
没有接到消息,则内核会让RunLoop处于休眠状态。source1
具备唤醒RunLoop的能力,可以捕获消息事件,发送给不具备唤醒RunLoop能力的source0
分发事件处理。