重新认识iOS中的Runloop

45 阅读4分钟

如何理解Runloop

RunLoop是系统内部维护的事件循环(EventLoop)来对消息/事件进行处理的一个对象。

事件循环(EventLoop):没有消息处理时,休眠以避免资源占用(从用户态切换到内核态)。有消息处理时,线程立刻被唤醒(从内核态切换到用户态)。

Runloop的数据结构

RunLoop的数据结构主要包括以下几个部分:CFRunLoop、CFRunLoopMode、Source/Timer/Observer

1. CFRunLoop:这是RunLoop的主要组成部分,负责整个线程的执行管理。

CFRunLoop:
    pthread:一一对应(线程与Runloop的关系)
    currentMode:CFRunloopMode
    modes:NSMutableSet<CFRunloopMode*>  Runloop与mode是一对多的关系
    commonModes:NSMutableSet<NSString*>
    commonModeItems:多个ObserverTimerSource的集合 

2.CFRunloopMode:

    name:NSDefaultRunloopMode
    sources0:CFRunloopSource集合
    sources1:CFRunloopSource集合
    observers:数组
    timers:数组 
    以上可以看出mode与source、timer、observer也是一对多的关系

3.CFRunloopSource:

    source0:需要手动唤醒线程
    source1:有主动唤醒线程的能力 

4.CFRunloopTimer:

    基于事件的定时器,与NSTimer有免费桥转换(tool-free bridge)

5.CFRunloopObserver:

    观测时间点 
    kCFRunloopEntry
    kCFRunloopBeforeTimers
    kCFRunloopBeforeSources
    kCFRunloopBeforeWaiting
    kCFRunloopAfterWaiting
    kCFRunloopExit

Runloop具体怎样维护的事件循环EventLoop?

截屏2023-12-09 下午9.39.03.png RunLoop使用do-while循环来处理事件队列中的事件。当没有新事件时,RunLoop会调用pthread_cond_wait进入内核态,让出CPU控制权给其他线程;当有新事件进入队列时,RunLoop会调用pthread_cond_signal()产生信号pthread_cond_broadcast()广播至线程重新开始处理这些事件。

Runloop的mode有哪几种

共有五种运行模式,分别是:

  1. kCFRunLoopDefaultMode:这是App的默认Mode,通常主线程是在这个Mode下运行。
  2. UITrackingRunLoopMode:这是界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响。
  3. UIInitializationRunLoopMode:这是在刚启动App时进入的第一个Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
  4. GSEventReceiveRunLoopMode:这是接受系统事件的内部Mode,通常用不到。
  5. kCFRunLoopCommonModes:这是一个占位用的Mode,作为标记kCFRunLoopDefaultModeUITrackingRunLoopMode

请注意,最后一种Mode——commonMode不是一种真正的Mode,而是一种特定的标识类型Mode,是一种同步Source/Timer/Observer到多种模式中的一种技术方案。在其标识的类型Mode中,添加事件处理意味着在其中的模式中均可以处理事件的回调。一般默认的commonMode中包含有defaultMode、trackingMode还有modalMode,可使用CFRunLoopAddCommonMode方法向CommonModes中添加自定义Modes。

系统中有哪些地方用到了Runloop

  1. UI线程:iOS的UI更新只在主线程上执行。因此,为了在UI上显示一些更新,你需要将更新放在主线程的RunLoop中。例如,如果你有一个自定义的动画或者你需要在用户交互后更新一些UI元素,你可能需要使用RunLoop。
  2. 定时器:NSTimer是iOS中创建定时器的类。这个类使用了RunLoop来处理定时任务。当你创建一个NSTimer对象后,它会被添加到RunLoop中,这样就可以在指定的时间间隔后触发一个回调。
  3. 触摸事件:iOS设备上的触摸事件(例如,用户点击屏幕或者移动手指)也是通过RunLoop处理的。这些事件首先被送到应用程序的主线程,然后由RunLoop进行处理。
  4. 异步处理:如果你正在进行一些异步处理(例如,从网络加载数据),你可能会使用RunLoop来处理这些任务。你可以创建一个新的NSOperation或者使用Grand Central Dispatch来在后台线程上执行任务,然后在任务完成后将结果传递回主线程的RunLoop。
  5. 定时任务:如果你需要定期执行一些任务(例如,定期检查应用程序的状态或者定期更新应用程序的数据),你可能会使用RunLoop来处理这些任务。你可以创建一个新的NSTimer对象,然后将其添加到RunLoop中。

Runloop与线程的关系

RunLoop与线程的关系非常密切,它们是一一对应的关系。每条线程,包括程序的主线程,都有与之相应的RunLoop对象。

  1. 线程创建:当线程刚创建时,并没有RunLoop对象。RunLoop会在第一次获取它时创建。
  2. Runloop启动:主线程的RunLoop默认是启动的,子线程的RunLoop默认是不开启的,需要我们自己手动开启循环。
  3. 线程运行:在线程运行过程中,RunLoop会负责维护线程的状态(唤醒、休眠),包括处理输入事件、调度任务、更新UI等。
  4. 线程结束:当线程执行完全部任务或者手动停止时,RunLoop会随之销毁。RunLoop销毁时机是在线程结束时。