RunLoop原理及应用

原理 一个形象的比喻来说明
进程是一家工厂,线程是一个流水线,Run Loop就是流水线上的主管;当工厂接到商家的订单分配给这个流水线时,Run Loop就启动这个流水线,让流水线动起来,生产产品;当产品生产完毕时,Run Loop就会暂时停下流水线,节约资源 。
管理流水线,流水线才不会因为无所事事被工厂销毁;而不需要流水线时,就会辞退这个主管,即退出线程,把所有资源释放 。
并不是iOS平台的专属概念,在任何平台的多线程编程中,为控制线程的生命周期,接收处理异步消息都需要类似的循环机制实现,的就是类似的机制 。
定义
当有持续的异步任务需求时,我们会创建一个独立的生命周期可控的线程 。就是控制线程生命周期并接收事件进行处理的机制 。
是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统 。
特性对象
// Foundation[NSRunLoop mainRunLoop];// 获取主线程的 RunLoop 对象[NSRunLoop currentRunLoop];// 获取当前线程的 RunLoop 对象// Core FoundationCFRunLoopGetMain();// 获取主线程的 RunLoop 对象CFRunLoopGetCurrent();// 获取当前线程的 RunLoop 对象
关键代码解析
1.通知 : 要开始进入 loop 了 。紧接着就进入 loop 。代码如下:
//通知 observersif (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);//进入 loopresult = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
2.开启一个 do while 来保活线程 。通知 : 会触发 Timer 回调、 回调,接着执行加入的 block 。代码如下:
// 通知 Observers RunLoop 会触发 Timer 回调if (currentMode->_observerMask & kCFRunLoopBeforeTimers)__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);// 通知 Observers RunLoop 会触发 Source0 回调if (currentMode->_observerMask & kCFRunLoopBeforeSources)__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);// 执行 block__CFRunLoopDoBlocks(runloop, currentMode);
接下来,触发回调,如果有是 ready 状态的话,就会跳转到去处理消息 。代码如下
if (MACH_PORT_NULL != dispatchPort ) {Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)if (hasMsg) goto handle_msg;}
3.回调触发后,通知 : 的线程将进入休眠(sleep)状态 。代码如下:
【RunLoop原理及应用】Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);if (!poll && (currentMode->_observerMask & kCFRunLoopBeforeWaiting)) {__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);}
4.进入休眠后,会等待的消息,以再次唤醒 。
只有在下面四个事件出现时才会被再次唤醒:
等待唤醒的代码:
do {__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {// 基于 port 的 Source 事件、调用者唤醒if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {break;}// Timer 时间到、RunLoop 超时if (currentMode->_timerFired) {break;}} while (1);
唤醒时通知 : 的线程刚刚被唤醒了 。代码如下:

RunLoop原理及应用

文章插图
if (!poll && (currentMode->_observerMask & kCFRunLoopAfterWaiting))__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
5. 被唤醒后就要开始处理消息了:如果是 Timer 时间到的话,就触发 Timer 的回调;如果是的话,就执行 block;如果是事件的话,就处理这个事件 。消息执行完后,就执行加到 loop 里的 block 。代码如下
handle_msg:// 如果 Timer 时间到,就触发 Timer 回调if (msg-is-timer) {__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())} // 如果 dispatch 就执行 blockelse if (msg_is_dispatch) {__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);} // Source1 事件的话,就处理这个事件else {CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);if (sourceHandledThisLoop) {mach_msg(reply, MACH_SEND_MSG, reply);}}
6.根据当前的状态来判断是否需要走下一个 loop 。当被外部强制停止或 loop 超时时,就不继续下一个 loop 了,否则继续走下一个 loop。代码如下:
if (sourceHandledThisLoop && stopAfterHandle) {// 事件已处理完retVal = kCFRunLoopRunHandledSource;} else if (timeout) {// 超时retVal = kCFRunLoopRunTimedOut;} else if (__CFRunLoopIsStopped(runloop)) {// 外部调用者强制停止retVal = kCFRunLoopRunStopped;} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {// mode 为空,RunLoop 结束retVal = kCFRunLoopRunFinished;}
处理流程图如下:
机制
主线程 (有的线程) 几乎所有函数都从以下六个之一的函数调起:
用于向外部报告当前状态的更改,框架中很多机制都由触发,如
消息通知、非延迟的、调用、block回调、KVO
延迟的, 延迟调用,定时器
由和内核管理,Mach port驱动,如、
输入源 按照同步异步分类
按照类型分类
触摸事件、::
基于Port的线程间的通信,系统事件的捕捉.
(两个线程之间相互传递消息的处理,系统事件捕捉,其实也包括触摸事件,只是把事件捕捉到以后传递给).
Timer
定时器,::(这个方法的底层实现也就是来实现的)
用于监听的状态,UI的刷新(), pool()
(在休眠之前都会去执行UI的刷新啊、 pool的释放等)
在启动之前,必须添加监听的输入源事件或者定时源事件,否则调用[ run]会直接返回,而不会进入循环让线程长驻 。
如果没有添加任何输入源事件或Timer事件,线程会一直在无限循环空转中,会一直占用CPU时间片,没有实现资源的合理分配 。
没有while循环且没有添加任何输入源或Timer的线程,线程会直接完成,被系统回收 。
//错误做法NSRunLoop *runLoop = [NSRunLoop currentRunLoop];while (!self.isCancelled && !self.isFinished) {[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];};//正确做法NSRunLoop *runLoop = [NSRunLoop currentRunLoop];[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];while (!self.isCancelled && !self.isFinished) {@autoreleasepool {[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];}}
的运行模式
的运行模式共有5种,只会运行在一个模式下,要切换模式,就要暂停当前模式,重写启动一个运行模式 。每一种事件源添加进的时候
- kCFRunLoopDefaultMode, App的默认运行模式,通常主线程是在这个运行模式下运行- UITrackingRunLoopMode, 跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)- kCFRunLoopCommonModes, 伪模式,不是一种真正的运行模式- UIInitializationRunLoopMode:在刚启动App时第进入的第一个Mode,启动完成后就不再使用- GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
有很多种模式,对应的只有一种.
1.它是代表的运行模式
2.一个包含若干个Mode,每个Mode又包含若干个//Timer/
3.启动时只能选择其中一个Mode,作为
4.如果需要切换Mode,只能退出当前,再重新选择一个Mode进入
5.不同组的//Timer/能分割开来,互不影响
6.如果Mode里面没有任何//Timer/,会立马退出
2.和区别:
不能主动触发事件 。只有一个回调(函数指针),使用时先调用l ()将标记为待处理,然后调用 ()唤醒,让处理事件 。
能主动唤醒的线程 。有一个和一个回调(函数指针),被用于通过内核和其他线程相互发送消息 。
的六个状态
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry , // 进入 loopkCFRunLoopBeforeTimers , // 触发 Timer 回调kCFRunLoopBeforeSources , // 触发 Source0 回调kCFRunLoopBeforeWaiting , // 等待 mach_port 消息kCFRunLoopAfterWaiting ), // 接收 mach_port 消息kCFRunLoopExit , // 退出 loopkCFRunLoopAllActivities// loop 所有状态改变}
的应用