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。代码如下:
//通知 observers
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
//进入 loop
result = __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)状态。代码如下:
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);
唤醒时通知 : 的线程刚刚被唤醒了。代码如下:
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 就执行 block
else 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 所有状态改变
}
的应用