RunLoop 原理及核心机制

2023-05-27



RunLoop 原理及核心机制







从iOS开始就没有对RunLoop进行过深入的研究,非常惭愧。碰巧前阵子负责性能优化项目,需要利用RunLoop进行性能优化和性能测试,借此机会对RunLoop的原理和特点进行了深入的研究。


定义RunLoop

如果需要持续的异步任务,我们将创造一个独立的生命周期可控的过程。RunLoop是一种控制线程生命周期并接受事件处理的机制。


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


Foundation: NSRunLoop
Core Foundation: CFRunLoop 核心部分,代码开源,C 语言写作,跨平台


目的

通过RunLoop机制实现节能、平稳、快速响应、良好的客户体验。


理解

过程是一家工厂,过程是一条流水线,Run 在流水线上,Loop就是主管;当工厂收到商家的订单分配给这条流水线时,Run Loop开始了这条流水线,让流水线移动,进行生产;当产品完成后,Run Loop将暂时停止流水线,节约能源。
RunLoop管理流水线,流水线不会因为无所事事而被工厂销毁;而且不需要流水线的时候,RunLoop这个主管就会被解雇,也就是退出流程,释放全部资源。


RunLoop并不是iOS平台的专属概念,在任何平台的多线程编程中,Android的Looper都需要类似RunLoop的循环机制来实现,以控制线程的生命周期。


特点

  • 当使用启动时,主线程的RunLoop将自动建立
  • 另外一个过程需要在这个过程下自行启动。
  • 无法建立自己的RunLoop
  • RunLoop不是线程安全的,所以需要避免将RunLoop调用到其他过程中。
  • 负责管理autoreleaseasease的RunLoop pools
  • RunLoop负责处理消息事件,即输入源事件和记时器事件

RunLoop机制

主线程 (有 RunLoop 的进程) 几乎所有函数都是从以下六个函数中调整起来的:


  • CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
    CFRunloop is calling out to an abserver callback function
    用来向外界报告 RunLoop 当前状态的变化,框架中的许多机制都是由 RunLoopObserver 触发,如 CAAnimation
  • CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
    CFRunloop is calling out to a block
    消息通知,perform,非延迟、调用dispatch,回调block,KVO
  • CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
    CFRunloop is servicing the main desipatch queue
  • CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
    CFRunloop is calling out to a timer callback function
    perform延迟, 延迟dispatch调用
  • CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0__PERFORM_FUNCTION
    CFRunloop is calling out to a source 0 perform function
    处理App内部事件,App本身负责管理(触发),例如UIEvent。、CFSocket。通用函数调用,系统调用
  • CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
    CFRunloop is calling out to a source 1 perform function
    由RunLoop和核心管理,Mach CFMachPort驱动程序、CFMessagePort
  • RunLoop 架构




  • RunLoop 运行时



以下六种状态主要有:


  • kCFRunLoopEntry -- 进入runloop循环循环循环
  • kCFRunLoopBeforeTimers -- 定期调用前回调处理
  • kCFRunLoopBeforeSources -- 处理input sources事件
  • kCFRunLoopBeforeWaiting -- 睡前调用runloop
  • kCFRunLoopAfterWaiting -- 唤起后调用runloop
  • kCFRunLoopExit -- 退出runloop

RunLoop 运转时调用栈

  • 运行主线程App时



  • ObserverRunLoopAutorelease Pool的关系



UIKit 通过 RunLoopObserver 在 RunLoop 两次 Sleep 间对 Autorelease Pool 进行 Pop 和 Push 将这次 Loop 中产生的 Autorelease 目标释放。


  • 挂起和唤起RunLoop



mach端口_port
调用mach_msg监控唤起端口,唤起前系统内核将此线程挂起,停留在mach_msg_trap状态。
这个端口的msg由另一个过程发送到核心后,trap状态被唤起,RunLoop继续工作。



消息事件支持RunLoop(Events)

  • RunLoop



  • 支持输入源的接收处理(Input Source)包括:

Mach系统 Port事件,是一种通信事件
定制输入事件



  • 对处理定时源的支持(Timer)事件
  • 启动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.isCancelled && !self.isFinished) {
    @autoreleasepool {
        [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
    }
}

Run Loop Modes

  • 理解
    Run Loop Mode是流水线上支持生产的产品类型。流水线只能在一定时间内以一种模式运行,生产某种类型的产品。新闻事件是订单。
  • Cocoa定义了四中Mode

Default:NSDefaultRunLoopMode,Run是默认的 当Loop没有指定Mode时,默认情况下,它会在Default上运行。 Mode下
Connection:NSConnectionReplyMode,对NSConnection事件进行监控处理。
Modal:NSModalPanelRunLoopMode,OS Modal面板事件X
Event tracking:UITrackingRunLoopMode,拖动事件
Common mode:NSRunLoopCommonModes,这是一种集合方式。当一个事件来源被绑定到这个模式集合时,它相当于绑定到集合中的每一种方式。



  • RunLoop可以通过[acceptInputForMode:beforeDate:]和[runMode:beforeDate:]指定一段时间内的运行模式。如果没有指定,RunLoop默认会在Default下运行(runModede反复调用):NSDefaultRunLoopMode beforDate:)
  • 启动主线程中的记时器Timer,然后拖动UITableView或UIScrollView,记时器不会执行。因为,为了更好的客户体验,Event在主线程中。 优先考虑tracking模式。客户拖动控件时,主线程的Run 在Event中运行Loop tracking 在Mode下,建立的Timer默认与Defaultt相关联。 Mode,所以系统不会立即执行Default 接受Mode下的事件。解决方法:
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                                   target:self
                                                 selector:@selector(timerFireMethod:)
                                                 userInfo:nil
                                                  repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 
//或 
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

[timer fire];

Run Loop应用实践

Run Loop主要有以下三个应用领域:


  • 维持过程的生命周期,使过程不会自动退出,isFinished在Yes时退出。
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!self.isCancelled && !self.isFinished) {
    @autoreleasepool {
            [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
    }
}
  • 建立一个长期的停留过程,执行一些将永远存在的任务。这个过程的生命周期和应用程序一样。
@autoreleasepool {
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
}
  • 监控某一事件,或者在一定时间内执行某一任务的过程。
    下面的代码,在30分钟内,每30秒执行onTimerFired。:。这样的情况一般都会出现,比如我需要在使用之后,在一定的时间内不断更新某些数据。
@autoreleasepool {
    NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
    NSTimer * udpateTimer = [NSTimer timerWithTimeInterval:30
                                                    target:self
                                                  selector:@selector(onTimerFired:)
                                                  userInfo:nil
                                                   repeats:YES];
    [runLoop addTimer:udpateTimer forMode:NSRunLoopCommonModes];
    [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60*30]];
}
  • RunLoop在AFNetworking中的建立
  (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
         // 这儿主要是监控一个 port,目的就是让这个 Thread 不会回收
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; 
        [runLoop run];
    }
}

  (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread =
        [[NSThread alloc] initWithTarget:self
                                selector:@selector(networkRequestThreadEntryPoint:)
                                  object:nil];
        [_networkRequestThread start];
    });
    return _networkRequestThread;
}




本文仅代表作者观点,版权归原创者所有,如需转载请在文中注明来源及作者名字。

免责声明:本文系转载编辑文章,仅作分享之用。如分享内容、图片侵犯到您的版权或非授权发布,请及时与我们联系进行审核处理或删除,您可以发送材料至邮箱:service@tojoy.com