RunLoop源码观察
源码可以在这里下载
文件 CFRunLoop.c CFRunLoop.h
这篇是在看了ibireme的深入理解RunLoop后自己对照源码并编写测试的一些记录
CFRunLoopMode
结构
1 | typedef struct __CFRunLoopMode *CFRunLoopModeRef; |
CFRunLoop
结构
1 | struct __CFRunLoop { |
获取RunLoop
存储<key:thread,value:runloop>全局字典默认是没有的,也就是说App初始化时是没有任何RunLoop的,在获取RunLoop时如果没有,就会创建,并存储在这个全局字典里
1 | //获取MainRunLoop |
创建RunLoop
1 | static CFRunLoopRef __CFRunLoopCreate(pthread_t t) { |
modeItem的种类
modeName的种类可以在这里看到更多
文档中关于RunLoop sources的描述
A run loop receives events from two different types of sources. Input sources deliver asynchronous events, usually messages from another thread or from a different application. Timer sources deliver synchronous events, occurring at a scheduled time or repeating interval. Both types of source use an application-specific handler routine to process the event when it arrives.
CFRunLoopSourceRef
是事件源(输入源)
比如点击事件,触摸事件等
按照官方文档分类,Source可分为:
Port-Base Source
:基于端口,和其他线程进行交互Custom Input Source
:自定义输入源Cocoa Perform Selector Source
:performSelector相关函数
按照函数调用栈,Source可分为:
Source0
:非基于Port的,接收点击事件,触摸事件等等
- 比如某个按钮点击的调用栈是添加Source0给RunLoop
performSelector
没有delay时 是添加source0给RunLoop- 但是在
performSelector
有delay时 是添加TimerSource给RunLoop
Source1
:基于Port的,通过内核和其他线程通信,接收,分发系统事件(大部分事件都是由屏幕表面包装成Event,然后分发下去,由Source0去处理)
ibireme博客的解释
Source有两个版本:Source0 和 Source1。
• Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
• Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。
关于mach port
的介绍可以查看这里(进程间通信)
各种modeItem的结构
1 | struct __CFRunLoopSource { |
RunLoop运行
文档中的描述
- Notify observers that the run loop has been entered.
- Notify observers that any ready timers are about to fire.
- Notify observers that any input sources that are not port based are about to fire.
- Fire any non-port-based input sources that are ready to fire.
- If a port-based input source is ready and waiting to fire, process the event immediately. Go to step 9.
- Notify observers that the thread is about to sleep.
- Put the thread to sleep until one of the following events occurs:
- An event arrives for a port-based input source.
- A timer fires.
- The timeout value set for the run loop expires.
- The run loop is explicitly woken up.
- Notify observers that the thread just woke up.
- Process the pending event.
- If a user-defined timer fired, process the timer event and restart the loop. Go to step 2.
- If an input source fired, deliver the event.
- If the run loop was explicitly woken up but has not yet timed out, restart the loop. Go to step 2.
- Notify observers that the run loop has exited.
翻译:
- 通知Observers准备进入RunLoop了
- 通知Observers准备执行Timers了
- 通知OBservers准备执行非Port-Base的inputSource了
- 执行非Port-Base的inputSource
- 如果有Port-Base的事件,并且等待执行,跳到第9步
- 通知Observers RunLoop没事情做了,准备休眠了(挂起)
- RunLoop休眠,直到下面任何一个事情发生
- 产生了一个Port-Base的source
- 定时器时间到了
- RunLoop设置的执行时长到了
- RunLoop被显式唤起
- 通知Observers RunLoop要被唤醒了
- 处理事件
- 如果一个用户自定义的Timer定时器到了,执行定时器指定的方法,并重启RunLoop循环(restart RunLoop 就是继续执行RunLoop 的do-while循环),跳到第2步
- 如果是一个InputSource,则派发事件(比如屏幕点击,触摸等等)
- 如果是被显式唤醒而且没有超过RunLoop指定的执行时长,则重启RunLoop循环
- 通知Observers RunLoop要退出了
注意:进入这里循环前,首先必须是当前RunLoop在指定Mode下,是有modeItems的,如果没有,会直接退出RunLoop,不会进入主循环
1 | void CFRunLoopRun(void) { /* DOES CALLOUT */ |
还可以看看这篇,一步一步的将RunLoop的实现展现出来