type
status
date
slug
summary
tags
category
icon
password
认识Runloop一文中,到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. 职责

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

三、Mode对象

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

1. CommonModes

经常会遇到下面代码,保证了timer 在滑动时也生效。那么是如何做到的呢?
在RunLoop对象中,有一个叫CommonModes的概念。先看RunLoop对象的组成:
一个 Mode 可以将自己标记为”Common”属性,通过将其 ModeName 添加到 RunLoop 的 “_commonModes” 中。
那么添加进去之后的作用是什么?
每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。其底层实现如下:
 
总结下,当一个timer被添加到kCFRunLoopCommonModes (NSRunLoopCommonModes),实际上,NSRunLoopCommonModes 是一个运行循环模式集合,这个集合里,将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。

四、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对象