进程是一家工厂,线程是一个流水线,Run Loop就是流水线上的主管;当工厂接到商家的订单分配给这个流水线时,Run Loop就启动这个流水线,让流水线动起来,生产产品;当产品生产完毕时,Run Loop就会暂时停下流水线,节约资源。
RunLoop管理流水线,流水线才不会因为无所事事被工厂销毁;而不需要流水线时,就会辞退RunLoop这个主管,即退出线程,把所有资源释放。
RunLoop并不是iOS平台的专属概念,在任何平台的多线程编程中,为控制线程的生命周期,接收处理异步消息都需要类似RunLoop的循环机制实现,Android的Looper就是类似的机制。
当有持续的异步任务需求时,我们会创建一个独立的生命周期可控的线程。RunLoop就是控制线程生命周期并接收事件进行处理的机制。
RunLoop是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统。
RunLoop
:NSRunLoop
(是CFRunLoopRef
的封装,提供了面向对象的 API)CFRunLoopRef
NSRunLoop
和CFRunLoopRef
都代表着RunLoop
对象NSRunLoop
不开源,而CFRunLoopRef
是开源的:CFRunLoopRef 源码RunLoop
对象的方式: // Foundation[NSRunLoop mainRunLoop]; // 获取主线程的 RunLoop 对象[NSRunLoop currentRunLoop]; // 获取当前线程的 RunLoop 对象// Core FoundationCFRunLoopGetMain(); // 获取主线程的 RunLoop 对象CFRunLoopGetCurrent(); // 获取当前线程的 RunLoop 对象
1.通知 observers:RunLoop 要开始进入 loop 了。紧接着就进入 loop。代码如下:
//通知 observers
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
//进入 loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
2.开启一个 do while 来保活线程。通知 Observers:RunLoop 会触发 Timer 回调、Source0 回调,接着执行加入的 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);
接下来,触发 Source0 回调,如果有 Source1 是 ready 状态的话,就会跳转到 handle_msg 去处理消息。代码如下
if (MACH_PORT_NULL != dispatchPort ) {Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)if (hasMsg) goto handle_msg;
}
3.回调触发后,通知 Observers:RunLoop 的线程将进入休眠(sleep)状态。代码如下:
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
if (!poll && (currentMode->_observerMask & kCFRunLoopBeforeWaiting)) {__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
4.进入休眠后,会等待 mach_port 的消息,以再次唤醒。
只有在下面四个事件出现时才会被再次唤醒:
等待唤醒的代码:
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);
唤醒时通知 Observer:RunLoop 的线程刚刚被唤醒了。代码如下:
if (!poll && (currentMode->_observerMask & kCFRunLoopAfterWaiting))__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
5.RunLoop 被唤醒后就要开始处理消息了:如果是 Timer 时间到的话,就触发 Timer 的回调;如果是 dispatch 的话,就执行 block;如果是 source1 事件的话,就处理这个事件。消息执行完后,就执行加到 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.根据当前 RunLoop 的状态来判断是否需要走下一个 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;
}
处理流程图如下:
主线程 (有 RunLoop 的线程) 几乎所有函数都从以下六个之一的函数调起:
用于向外部报告 RunLoop 当前状态的更改,框架中很多机制都由 RunLoopObserver 触发,如 CAAnimation
消息通知、非延迟的perform、dispatch调用、block回调、KVO
延迟的perform, 延迟dispatch调用,定时器
处理App内部事件、App自己负责管理(触发),如UIEvent、CFSocket。普通函数调用,系统调用
由RunLoop和内核管理,Mach port驱动,如CFMachPort、CFMessagePort
按照类型分类
Source0
触摸事件、performSelector:onThread:
Source1
基于Port的线程间的通信,系统事件的捕捉.
(两个线程之间相互传递消息的处理,系统事件捕捉,其实也包括触摸事件,只是把事件捕捉到以后传递给Source0).
Timer
NSTimer定时器,performSelector:withObject:afterDelay(这个方法的底层实现也就是NSTimer来实现的)
Observers
用于监听RunLoop的状态,UI的刷新(BeforeWaiting),Autorelease pool(BeforeWaiting)
(在RunLoop休眠之前都会去执行UI的刷新啊、Autorelease pool的释放等)
在启动RunLoop之前,必须添加监听的输入源事件或者定时源事件,否则调用[runloop 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]];}
}
RunLoop的运行模式共有5种,RunLoop只会运行在一个模式下,要切换模式,就要暂停当前模式,重写启动一个运行模式。每一种事件源添加进Runloop的时候
- kCFRunLoopDefaultMode, App的默认运行模式,通常主线程是在这个运行模式下运行
- UITrackingRunLoopMode, 跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)
- kCFRunLoopCommonModes, 伪模式,不是一种真正的运行模式
- UIInitializationRunLoopMode:在刚启动App时第进入的第一个Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
RunLoop有很多种模式,对应的_currentMode只有一种.
CFRunLoopModeRef
1.CFRunLoopModeRef它是代表RunLoop的运行模式
2.一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
3.RunLoop启动时只能选择其中一个Mode,作为currentMode
4.如果需要切换Mode,只能退出当前RunLoop,再重新选择一个Mode进入
5.不同组的Source0/Source1/Timer/Observer能分割开来,互不影响
6.如果Mode里面没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
2.Source0
和Source1
区别:Source0
不能主动触发事件。Source0
只有一个回调(函数指针),使用时先调用 CFRunLoopSourceSignal (source)
将Source
标记为待处理,然后调用 CFRunLoopWakeUp (runloop)
唤醒 RunLoop
,让RunLoop
处理事件。Source1
能主动唤醒 RunLoop
的线程。Source1
有一个 mach_port
和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry , // 进入 loopkCFRunLoopBeforeTimers , // 触发 Timer 回调kCFRunLoopBeforeSources , // 触发 Source0 回调kCFRunLoopBeforeWaiting , // 等待 mach_port 消息kCFRunLoopAfterWaiting ), // 接收 mach_port 消息kCFRunLoopExit , // 退出 loopkCFRunLoopAllActivities // loop 所有状态改变
}
本文发布于:2024-02-04 15:41:13,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170710785156781.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |