type
status
date
slug
summary
tags
category
icon
password
在上文,我们了解到RunLoop不但表示的是一个事件循环,也是一个对象。
在本文,我们将会在源码的帮助下,窥探RunLoop对象,以及其相关对象。
源码的出处:CF源码Swift CF源码。笔者注读的源码:SourceCode
其中,关于RunLoop的类有:
notion image

一、RunLoop对象

1. CFRunLoopRef

获取RunLoop对象可以通过下面获得:
但苹果开放的源码,是Core Foundation,而以上获取的是Foundation
NSRunLoopCore Foundation对应的是CFRunLoopRef,源码中CFRunLoopRef的对应的结构体是
__CFRunLoop
__CFRunLoop作了一些删减,整理如下:
notion image

2. 获取RunLoop对象

如何获取RunLoop对象的实现如下:
notion image

3. RunLoop与线程

从上面获取RunLoop的实现里可以看出,_CFRunLoopGet0是获取RunLoop对象的底层实现函数,其中传入的参数,是一个线程对象。
那么线程与RunLoop的关系又是怎么样的?
读完上面源码,我们整理如下:
notion image

二、RunLoop相关类

1. 相关类

在需要了解更过RunLoop的类时,我们可以通过下面打印,获得足够多的信息,在五、完整的RunLoop对象列出RunLoop的完整信息:
整理输出的打印信息及结构,结合源码。
notion image
上图五个类,标记为三种颜色:
  • 红色:RunLoop运行循环的模式,CFRunLoopRefRunLoop对象本身,CFRunLoopModeRefRunLoop当前的一个运行模式。
  • 蓝色:需要RunLoop处理的消息类型,包括Source和timer。
  • 绿色:监听RunLoop运行状态的一个对象。

2. 职责

上面五个类的具体职责如下:
notion image
上面的蓝色标记的 Source/Timer/Observer 被统称为 mode item,在后面会详细讲述。

三、Mode对象

Mode表示RunLoop的运行模式,其CF对象CFRunLoopModeRef
notion image
其中,常见Mode,如下:
notion image

1. CommonModes

其中,需要着重说明的是,在RunLoop对象中,有一个叫CommonModes的概念。
先看RunLoop对象的组成:
一个 Mode 可以将自己标记为”Common”属性,通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中。
那么添加进去之后的作用是什么?
每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。其底层实现如下:

2. Mode API

CFRunLoop对外暴露的管理 Mode 接口只有下面2个:
Mode 暴露的管理 mode item 的接口有下面几个:
你只能通过 mode name 来操作内部的 mode,当你传入一个新的 mode name 但 RunLoop 内部没有对应 mode 时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef。对于一个 RunLoop 来说,其内部的 mode 只能增加不能删除。
苹果公开提供的 Mode 有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用这两个 Mode Name 来操作其对应的 Mode。
同时苹果还提供了一个操作 Common 标记的字符串:kCFRunLoopCommonModes (NSRunLoopCommonModes),你可以用这个字符串来操作 Common Items,或标记一个 Mode 为 “Common”。使用时注意区分这个字符串和其他 mode name。
如下:

四、Mode Item

RunLoop需要处理的消息,包括timer以及source消息,它们都属于Mode item。
RunLoop也可以被监听,被监听的对象是observer对象,也属于Mode item。
所有的mode item都可以被添加到Mode中,Mode中包含可以包含多个mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

1. CFRunLoopSourceRef

notion image
Source有两个版本:Source0Source1
  • Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
  • Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。

2. CFRunLoopTimerRef

notion image
CFRunLoopTimerRef是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

3. CFRunLoopObserverRef

CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。
notion image
下面是一个添加监听的示例,代码在这儿

五、完整的RunLoop对象

打印RunLoop,整理如下;
可以看到,系统默认注册了5个Mode:
  1. kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
  1. UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
  1. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
  1. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
  1. kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。

参考

链接

  1. CF源码
  1. Swift CF源码
  1. 深入理解RunLoop
  1. Threading Programming Guide--Runloop 中文翻译

示例代码

  1. 源码注读
  1. RunLoop对象