type
status
date
slug
summary
tags
category
icon
password
一、RunLoop对象
1. CFRunLoopRef
获取RunLoop对象可以通过下面获得:
但苹果开放的源码,是
Core Foundation
,而以上获取的是Foundation
。NSRunLoop
在Core Foundation
对应的是CFRunLoopRef
,源码中CFRunLoopRef
的对应的结构体是__CFRunLoop
。对__CFRunLoop
作了一些删减,整理如下:2. 获取RunLoop对象
如何获取RunLoop对象的实现如下:
3. RunLoop与线程
从上面获取RunLoop的实现里可以看出,
_CFRunLoopGet0
是获取RunLoop
对象的底层实现函数,其中传入的参数,是一个线程对象。那么线程与RunLoop的关系又是怎么样的?
读完上面源码,我们整理如下:
二、RunLoop相关类
1. 相关类
在需要了解更过RunLoop的类时,我们可以通过下面打印,获得足够多的信息,在本文【五、完整的RunLoop对象】列出RunLoop的完整信息:
整理输出的打印信息及结构,结合源码。
上图五个类,标记为三种颜色:
- 红色:
RunLoop
运行循环的模式,CFRunLoopRef
是RunLoop
对象本身,CFRunLoopModeRef
是RunLoop
当前的一个运行模式。
- 蓝色:需要RunLoop处理的消息类型,包括Source和timer。
- 绿色:监听RunLoop运行状态的一个对象。
2. 职责
上面五个类的具体职责如下:
上面的蓝色标记的 Source/Timer/Observer 被统称为 mode item,在后面会详细讲述。
三、Mode对象
Mode表示RunLoop的运行模式,其CF对象
CFRunLoopModeRef
。其中,常见Mode,如下:
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
Source有两个版本:
Source0
和 Source1
。Source0
只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒RunLoop
,让其处理这个事件。
Source1
包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒RunLoop
的线程,其原理在下面会讲到。
2. CFRunLoopTimerRef
CFRunLoopTimerRef
是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop
时,RunLoop
会注册对应的时间点,当时间点到时,RunLoop
会被唤醒以执行那个回调。3. CFRunLoopObserverRef
CFRunLoopObserverRef
是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop
的状态发生变化时,观察者就能通过回调接受到这个变化。下面是一个添加监听的示例,代码在这儿:
五、完整的RunLoop对象
打印
RunLoop
,整理如下;可以看到,系统默认注册了5个Mode:
- kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
- UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
- UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
- kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
参考
链接
示例代码