RunLoop

作者: Xcoder_ | 来源:发表于2021-09-24 13:58 被阅读0次

    RunLoop 简介

    image.png

    RunLoop是通过内部维护的事件循环(Event Loop)来对事件/消息进行管理的一个对象。

    • 运行循环,在程序运行过程中循环做一些事情(如接收消息、处理消息、休眠等待等);
    • RunLoop是通过内部维护的事件循环来对事件/消息进行管理的一个对象;
    • RunLoop不是一个简单的do...while循环,它涉及到用户态和内核态之间的切换。
    • 线程和RunLoop是一一对应的,其映射关系是保存在一个全局的 Dictionary 里
      自己创建的线程默认是没有开启RunLoop的

    事件循环

    事件循环就是对事件/消息进行管理,事件循环可以达到:

    • 没有消息需要处理时,休眠线程以避免资源占用。从用户态切换到内核态,等待消息;
    • 有消息需要处理时,立刻唤醒线程,回到用户态处理消息;
    • 通过调用mach_msg()函数来转移当前线程的控制权给内核态/用户态。

    RunLoop 的基本作用

    • 保持程序的持续运行:
      如果没有RunLoopmain()函数一执行完,程序就会立刻退出。
      而我们的 iOS 程序能保持持续运行的原因就是在main()函数中调用了UIApplicationMain函数,这个函数内部会启动主线程的RunLoop
    • 处理 App 中的的各种事件(比如触摸事件、定时器事件等);
    • 节省 CPU 资源,提高程序性能:该做事时做事,该休息时休息。

    各数据结构之间的联系

    线程和RunLoop一一对应, RunLoop和Mode是一对多的,Mode和source、timer、observer也是一对多的

    image

    RunLoop的Mode

    关于Mode首先要知道一个RunLoop 对象中可能包含多个Mode,且每次调用 RunLoop 的主函数时,只能指定其中一个 Mode(CurrentMode)。切换 Mode,需要重新指定一个 Mode 。主要是为了分隔开不同的 Source、Timer、Observer,让它们之间互不影响。

    image

    当RunLoop运行在Mode1上时,是无法接受处理Mode2或Mode3上的Source、Timer、Observer事件的

    总共是有五种CFRunLoopMode:

    • kCFRunLoopDefaultMode:默认模式,主线程是在这个运行模式下运行

    • UITrackingRunLoopMode:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)

    • UIInitializationRunLoopMode:在刚启动App时第进入的第一个 Mode,启动完成后就不再使用

    • GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到

    • kCFRunLoopCommonModes:伪模式,不是一种真正的运行模式,是同步Source/Timer/Observer到多个Mode中的一种解决方案

    RunLoop的实现机制

    image

    这张图在网上流传比较广。
    对于RunLoop而言最核心的事情就是保证线程在没有消息的时候休眠,在有消息时唤醒,以提高程序性能。RunLoop这个机制是依靠系统内核来完成的(苹果操作系统核心组件Darwin中的Mach)。

    image

    RunLoop通过mach_msg()函数接收、发送消息。它的本质是调用函数mach_msg_trap(),相当于是一个系统调用,会触发内核状态切换。在用户态调用 mach_msg_trap()时会切换到内核态;内核态中内核实现的mach_msg()函数会完成实际的工作。
    即基于port的source1,监听端口,端口有消息就会触发回调;而source0,要手动标记为待处理和手动唤醒RunLoop

    Mach消息发送机制
    大致逻辑为:
    1、通知观察者 RunLoop 即将启动。
    2、通知观察者即将要处理Timer事件。
    3、通知观察者即将要处理source0事件。
    4、处理source0事件。
    5、如果基于端口的源(Source1)准备好并处于等待状态,进入步骤9。
    6、通知观察者线程即将进入休眠状态。
    7、将线程置于休眠状态,由用户态切换到内核态,直到下面的任一事件发生才唤醒线程。

    • 一个基于 port 的Source1 的事件(图里应该是source0)。
    • 一个 Timer 到时间了。
    • RunLoop 自身的超时时间到了。
    • 被其他调用者手动唤醒。

    8、通知观察者线程将被唤醒。
    9、处理唤醒时收到的事件。

    • 如果用户定义的定时器启动,处理定时器事件并重启RunLoop。进入步骤2。
    • 如果输入源启动,传递相应的消息。
    • 如果RunLoop被显示唤醒而且时间还没超时,重启RunLoop。进入步骤2

    10、通知观察者RunLoop结束。

    RunLoop 的应用范畴

    • 定时器(Timer)、PerformSelector
    • GCD:dispatch_async(dispatch_get_main_queue(), ^{ });
    • 事件响应、手势识别、界面刷新
    • 网络请求
    • AutoreleasePool

    RunLoop 在实际开发中的应用

    • 使用端口或自定义输入源与其他线程进行通信
    • 在子线程上使用定时器
    • 解决NSTimer在滑动时停止工作的问题
    • 控制线程的生命周期,实现一个常驻线程
    • 在 Cocoa 应用程序中使用任何performSelector...方法
    • 监控应用卡顿
    • 性能优化
    • ......
    1、怎么创建一个常驻线程?

    1、为当前线程开启一个RunLoop(第一次调用 [NSRunLoop currentRunLoop]方法时实际是会先去创建一个RunLoop)
    1、向当前RunLoop中添加一个Port/Source等维持RunLoop的事件循环(如果RunLoop的mode中一个item都没有,RunLoop会退出)
    2、启动该RunLoop

    2、怎样保证子线程数据回来更新UI的时候不打断用户的滑动操作?

    当我们在子请求数据的同时滑动浏览当前页面,如果数据请求成功要切回主线程更新UI,那么就会影响当前正在滑动的体验。
    我们就可以将更新UI事件放在主线程的NSDefaultRunLoopMode上执行即可,这样就会等用户不再滑动页面,主线程RunLoop由UITrackingRunLoopMode切换到NSDefaultRunLoopMode时再去更新UI

    [self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
    
    

    相关文章

      网友评论

        本文标题:RunLoop

        本文链接:https://www.haomeiwen.com/subject/okisgltx.html