type
status
date
slug
summary
tags
category
icon
password
在之前的篇章中,对
RunLoop
有了基本认识,以及对其底层对象也有了一定了解。这篇描述的是"RunLoop是如何运行的?''为了解决这个问题,我们还是直接上源码,并给出导读的路径,如下:
在 CFRounLoop.c 类中:
__CFRunLoopRun >>> CFRunLoopRunSpecific >>> CFRunLoopRun
一、运行逻辑
上图左侧运行逻辑图出自于官方文档。在RunLoop中,接收输入事件来自两种不同的来源:输入源(input source)和定时源(timer source)。
1. 来源按同步异步分类
上图描述了按两种来源:异步方式接收的输入源,以及同步接收的定时源。
1.1 Input sources
输入源传递异步事件,通常消息来自于其他线程或程序,按照是否来源于内核也分为下面几种:
- Port-Based Sources,基于 Port的 事件,系统底层的,一般由内核自动发出信号。例如
CFSocketRef
,在应用层基本用不到。
- Custom Input Sources,非基于Port事件,用户手动创建的 Source,则必须从其他线程手动发送信号。
- Cocoa Perform Selector Sources, Cocoa 提供的
performSelector
系列方法,也是一种事件源。和基于端口的源一样,执行selector
请求会在目标线程上序列化,减缓许多在线程上允许多个方法容易引起的同步问题。不像基于端口的源,一个selector执行完后会自动从RunLoop
里面移除。
1.2 Timer sources
定时源则传递同步事件,发生在特定时间或者重复的时间间隔。
定时器可以产生基于时间的通知,但它并不是实时机制。和输入源一样,定时器也和你的
RunLoop
的特定模式相关。如果定时器所在的模式当前未被RunLoop
监视,那么定时器将不会开始直到RunLoop
运行在相应的模式下。其主要包含了两部分:
- NSTimer
- performSelector:withObject:afterDelay:
2. 来源按对象分类
在上一篇中,我们就是按对象将
RunLoop
接收事件按对象分类的。2.1 Source1
对应于Port-Based Sources,即基于Port的,通过内核和其他线程通信。
常用于接收、分发系统事件,大部分屏幕交互事件都是由
Source1
接收,包装成Event
,然后分发下去,最后由Source0
去处理。所以,其包括:
- 基于Port的线程间通信;
- 系统事件捕捉;
2.2 Source0
是非Port事件。在应用中,触摸事件的最终处理,以及
perforSelector:onThread
都是包装成该类型对象,最后由开发者指定回调函数,手动处理该事件。需要注意的是
perforSelector:onThread
是否有delay
,即是否延迟函数或者定时函数等类型。perforSelector:onThread
不是delay
函数时, 是Source0
事件。
performSelector:withObject:afterDelay
有delay
时,则属于Timers
事件。
所以,其包括:
- 触摸事件处理
- performSelector:onThread
2.3 Timers
同上 Timer sources 说明。
二、源码详解
RunLoop
主要运行逻辑源码都集中在CFRunLoop.c
,笔者进行了注读,而且抽取出RunLoopCycle.c
,方便查看。代码见文末参考-示例源码。也可以跳过该节,直接查看【三、流程图】,直观清晰的分析
RunLoop
的运行逻辑及详细流程。1. 入口函数
2. RunLoop执行函数
3. RunLoop消息处理函数
4. 消息处理底层函数
当
RunLoop
进行回调时,一般都是通过一个很长的函数调用出去 (call out),当你在你的代码中下断点调试时,打印堆栈(bt),就能在调用栈上看到这些函数。下面是这几个函数的整理版本,如果你在调用栈中看到这些长函数名,在这里查找一下就能定位到具体的调用地点了:
5. 休眠的实现
RunLoop如何实现真正意义上的休眠,而不是像下面这种方式:
其内部是通过内核态的调用。
三、流程图
将上面的代码逻辑抽取下面得到如下:
- 黄色:表示通知Observer各个阶段;
- 蓝色:处理消息的逻辑;
- 绿色:分支判断逻辑;
1. 执行逻辑
2. 流程图
参考
链接
示例源码