iOS RunLoop(理论基础)

309 阅读7分钟

概述

runloop官方文档

  • Runloop是一个与线程相关的基础结构
  • Runloop是一个事件处理循环,用于安排工作和协调传入事件的接收
  • Runloop的目的是在有工作要做时让线程保持忙碌,在没有工作时让线程进入睡眠状态
  • Runloop管理不是完全自动的,在设计线程代码的时候,必须在适当的时间启动运行循环
  • 在主线程中Runloop会自动启动,在子线程中需要手动的开启

Runloop解析

RunLoop接受事件主要来自Input sources输入源Timer sources时钟源

  • Input sources:主要来自其他线程或其他应用,发送异步事件
  • Timer sources:发送同步事件,发生在预定的时间或重复的时间间隔(occurring at a scheduled time or repeating interval)

官网有一张图做了总结:显示runloop和各种源的概念结构

截屏2021-09-27 下午3.33.11.png

  • Input sources将异步事件传递给相应的处理程序,并导致runUntilDate:方法(在线程的关联nsrunlop对象上调用)退出
  • Timer sources向其处理程序例程传递事件,但不会导致运行循环退出

RunLoop Mode

  • RunLoop Mode:是要监视的输入源和计时器的集合,以及要通知的运行循环观察者的集合
  • 每次运行run循环时,都会指定(显式或隐式)要运行的特定“模式”
  • 在运行循环的这一过程中,只有与该模式关联的源被监视并允许传递它们的事件

模式的类型

  • NSDefaultRunLoopMode:默认模式,一般情况下使用这个模式

  • NSConnectionReplyMode: 将此模式与NSConnection对象结合使用来监视回复

  • NSModalPanelRunLoopMode:使用这种模式来识别用于模态面板的事件

  • NSEventTrackingRunLoopMode: 使用该Mode跟踪来自用户交互的事件,例如:UIScrollView上下滑动

  • NSRunLoopCommonModes:这是一种组合模式,该组合默认包括默认、模态和事件跟踪模式

虽然官方文档中定义了5种模式,但是iOS中只暴露NSDefaultRunLoopModeNSRunLoopCommonModes两种

Input Source(输入源)

输入源以异步方式向线程传递事件。事件的源取决于输入源的类型,输入源通常为两类之一。基于端口的输入源监视应用程序的Mach端口。自定义输入源监视自定义事件源。运行循环而言,输入源是基于端口的还是自定义的并不重要。系统通常实现两种类型的输入源,您可以按原样使用。这两个信号源之间的唯一区别是它们的信号方式。基于端口的源由内核自动发出信号,自定义源必须从另一个线程手动发出信号

Port-Based Sources(基于端口的source)

  • 在Cocoa中,根本不需要直接创建输入源,只需创建一个端口对象,并使用NSPort的方法将该端口添加到运行循环中
  • 官方创建的案例:Configuring a Port-Based Input Source.

Custom Input Sources(自定义输入源)

  • 若要创建自定义输入源,必须使用与核心基础中的CFRunLoopSourceRef不透明类型相关联的函数。
  • 可以使用几个回调函数配置自定义输入源,Core Foundation在不同点调用这些函数来配置源,处理任何传入事件,并在从运行循环中移除源时拆掉源。
  • 有关如何创建自定义输入源的示例,Defining a Custom Input Source

Cocoa Perform Selector Sources

  • 除了基于端口的源之外,Cocoa还定义了一个自定义输入源,允许您在任何线程上执行Selector
  • 与基于端口的源一样,执行选择器的请求在线程上是序列化,从而减轻了在一个线程上运行多个方法时可能出现的许多同步问题
  • 与基于端口的源不同,执行选择器源在执行其选择器后将自己从运行循环中移除。

performSelectorOnMainThread:withObject:waitUntilDone: performSelectorOnMainThread:withObject:waitUntilDone:modes:

在应用程序的主线程的下一个运行循环周期中,在该线程上执行指定的选择器。这些方法允许您在执行选择器之前阻塞当前线程。

performSelector:onThread:withObject:waitUntilDone: performSelector:onThread:withObject:waitUntilDone:modes:

在具有NSThread对象的任何线程上执行指定的选择器。这些方法允许您在执行选择器之前阻塞当前线程。

performSelector:withObject:afterDelay: performSelector:withObject:afterDelay:inModes:

在下一个运行循环周期和可选延迟期之后,在当前线程上执行指定的选择器。因为它会等到下一个运行循环周期执行选择器,所以这些方法提供了一个来自当前执行代码的自动最小延迟。多个排队选择器按其排队顺序依次执行

cancelPreviousPerformRequestsWithTarget: cancelPreviousPerformRequestsWithTarget:selector:object:

取消一个通过performSelector:withObject:afterDelay:performSelector:withObject:afterDelay:inModes:发送的的消息,

Timer Sources

  • Timer Sources在将来的预设时间同步地向线程传递事件
  • Timer是线程通知自己做某事的一种方式
  • 虽然它生成基于时间的通知,但Timer不是实时机制,所以Timer可能不准
  • Input Source一样,timer与Runloop的特定模式相关联
  • 如果timer未处于运行循环当前监视的模式,则在timer支持的模式之一运行runLoop之前,它不会启动
  • 类似地,如果一个计时器在Runloop处于执行一个处理程序的中间时触发,则timer等到runloop的下一次调用它的处理程序。如果Runloop根本没有运行,则timer永远不会触发

有关配置计时器源的详细信息,请参阅:Configuring Timer Sources,参考资料:NSTimer Class ReferenceCFRunLoop Timer Reference

Run Loop Observers

  • 与在适当的异步或同步事件发生时触发的源不同,Run Loop ObserversRunLoop执行期间在特殊位置触发
  • 可以使用Run Loop Observers来准备Thread以处理给定事件,或者在Thread进入睡眠之前准备Thread,我们可以将Run Loop ObserversRunLoop中的以下事件关联:
  1. 进入RunLoop的时
  2. RunLoop即将处理Timer
  3. Runloop即将处理Input Source
  4. RunLoop即将进入睡眠状态时
  5. RunLoop唤醒时,但在它处理唤醒它的事件之前
  6. RunLoop退出时

要创建运行循环观察者,需要创建CFRunLoopObserverRef不透明类型的新实例。

The Run Loop Sequence of Events

每次运行它时,线程的RunLoop都会处理挂起的事件,并为任何连接的观察者生成通知。它这样做的顺序非常具体,如下所示:

  1. 通知observer已进入RunLoop
  2. 通知observer准备就绪的Timer即将启动
  3. 通知observer任何非基于端口的输入源即将触发。
  4. 启动任何准备启动的非基于端口的输入源。
  5. 如果基于端口的输入源已准备就绪并等待触发,请立即处理该事件。转至步骤9
  6. 通知observer线程即将休眠
  7. 将线程置于睡眠状态,直到发生以下事件之一:
    • 基于端口的输入源的事件到达。
    • 计时器启动。
    • 为运行循环设置的超时值过期。
    • 运行循环被显式唤醒。
  8. 通知观察者线程刚刚唤醒。
  9. 处理挂起的事件。
    • 如果触发了用户定义的计时器,请处理计时器事件并重新启动循环。转至步骤2
    • 如果触发了输入源,则传递事件。
    • 如果运行循环被显式唤醒但尚未超时,请重新启动循环。转至步骤2
  10. 通知观察者运行循环已退出

用一张图来总结上面的流程:

截屏2021-09-28 上午10.48.00.png

(什么时候使用runloop)When Would You Use a Run Loop?

  • 唯一需要显式运行运行循环的时间是为应用程序创建子线程时
  • 主线程的runloop,系统自动创建
  1. 使用端口或自定义输入源与其他线程通信
  2. 在线程上使用计时器。
  3. 在Cocoa应用程序中使用任何PerformSelect…方法
  4. 线程保活

总结,这篇文章主要是基于苹果官方文档的翻译,主要讲解了一些runloop的理论概念。