RunLoop原理及应用

阅读: 评论:0

RunLoop原理及应用

RunLoop原理及应用

RunLoop原理

一个形象的比喻来说明RunLoop      

        进程是一家工厂,线程是一个流水线,Run Loop就是流水线上的主管;当工厂接到商家的订单分配给这个流水线时,Run Loop就启动这个流水线,让流水线动起来,生产产品;当产品生产完毕时,Run Loop就会暂时停下流水线,节约资源。
        RunLoop管理流水线,流水线才不会因为无所事事被工厂销毁;而不需要流水线时,就会辞退RunLoop这个主管,即退出线程,把所有资源释放。

        RunLoop并不是iOS平台的专属概念,在任何平台的多线程编程中,为控制线程的生命周期,接收处理异步消息都需要类似RunLoop的循环机制实现,Android的Looper就是类似的机制。

Runloop定义

        当有持续的异步任务需求时,我们会创建一个独立的生命周期可控的线程。RunLoop就是控制线程生命周期并接收事件进行处理的机制。

        RunLoop是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统。

RunLoop特性

  • 主线程的RunLoop在应用启动的时候就会自动创建
  • 其他线程则需要在该线程下自己启动
  • 不能自己创建RunLoop
  • RunLoop并不是线程安全的,所以需要避免在其他线程上调用当前线程的RunLoop
  • RunLoop负责管理autorelease pools
  • RunLoop负责处理消息事件,即输入源事件和计时器事件

RunLoop 对象

  • iOS 中有 2 套 API 来访问和使用RunLoop
    ① Foundation:NSRunLoop(是CFRunLoopRef的封装,提供了面向对象的 API)
    ② Core Foundation:CFRunLoopRef
  • NSRunLoopCFRunLoopRef都代表着RunLoop对象
  • NSRunLoop不开源,而CFRunLoopRef是开源的:CFRunLoopRef 源码
  • 获取RunLoop对象的方式:
    // Foundation[NSRunLoop mainRunLoop];     // 获取主线程的 RunLoop 对象[NSRunLoop currentRunLoop];  // 获取当前线程的 RunLoop 对象// Core FoundationCFRunLoopGetMain();     // 获取主线程的 RunLoop 对象CFRunLoopGetCurrent();  // 获取当前线程的 RunLoop 对象

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 的消息,以再次唤醒。

只有在下面四个事件出现时才会被再次唤醒:

  • 基于 port 的 Source 事件;
  • Timer 时间到;
  • RunLoop 超时;
  • 被调用者唤醒。

等待唤醒的代码:


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 的线程) 几乎所有函数都从以下六个之一的函数调起:

  • CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION

        用于向外部报告 RunLoop 当前状态的更改,框架中很多机制都由 RunLoopObserver 触发,如 CAAnimation

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK

        消息通知、非延迟的perform、dispatch调用、block回调、KVO

  • CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
  • CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION

        延迟的perform, 延迟dispatch调用,定时器

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
  • 处理App内部事件、App自己负责管理(触发),如UIEvent、CFSocket。普通函数调用,系统调用

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION

        由RunLoop和内核管理,Mach port驱动,如CFMachPort、CFMessagePort

 

RunLoop输入源

按照同步异步分类

  • 一种是来自另一个线程或者来自不同应用的异步消息;(不是当前线程触发的?)
  • 另一种是来自预订时间或者重复间隔的同步事件。(本线程的事件?)

按照类型分类

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的运行模式

        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.Source0Source1区别:
Source0不能主动触发事件。Source0只有一个回调(函数指针),使用时先调用 CFRunLoopSourceSignal (source)Source 标记为待处理,然后调用 CFRunLoopWakeUp (runloop) 唤醒 RunLoop,让RunLoop处理事件。
Source1 能主动唤醒 RunLoop 的线程。Source1有一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。

RunLoop 的六个状态


typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry , // 进入 loopkCFRunLoopBeforeTimers , // 触发 Timer 回调kCFRunLoopBeforeSources , // 触发 Source0 回调kCFRunLoopBeforeWaiting , // 等待 mach_port 消息kCFRunLoopAfterWaiting ), // 接收 mach_port 消息kCFRunLoopExit , // 退出 loopkCFRunLoopAllActivities  // loop 所有状态改变
}

RunLoop的应用

本文发布于:2024-02-04 15:41:13,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170710785156781.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:原理   RunLoop
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23