本文的源码来做苹果的CoreFoudation版本 CF-855.17
, 源码地址
前序
消息驱动机制
1.现代计算机的工作模式都是熟知的消息驱动机制
.所谓消息驱动就是有消息(中断(硬中断\软中断)
)了后触发相应的操作, 由操作系统
包装成相应的数据结构
, 定位
到目标进程并派发给定位的进程
进行处理.
2. 由1的基本原理,可以明显的推导出一个进程要想被消息驱动, 必须要求当前的进程在消息派发到位之前一直是存活的, 所以映射到代码的层面, 本质就是死循环
, 但是和c里的死循环不一样的是, 进程在等待消息的时候, 是不占用cpu资源
的, 他的响应是被动
的, 操作系统
包装完硬件的中断后, 主动派发
给目标进程做处理, 当然不占用cpu资源的实现是很复杂的, 系统调度
算法不是现在讨论的
硬件中断
鼠标移动点击, 键盘点击, 游戏手柄操作等
异步网络信息的回调, 通过网卡的端口造成硬件中断
时钟中断, 操作系统的定时器就是根据时钟中断实现的, 只有内核能检查到时钟中断, 并且时钟中断不能被屏蔽
CPU内部异常中断, 如除数为0的时候, 或者多核CPU之间互相通信的中断
...
软件中断
操作系统提供的 系统调用 比如汇编里 int指令
代码里定时器的回调 操作系统利用时钟中断,然后不同语言平台根据系统的接口实现的定时回调功能, 如OC里的NSTimer, dispatch_timer, 追根究底是利用时钟中断机制实现的
线程异步通信 最直接的是OC里的线程通信机制
进程之间的通信(端口管道之间)
各种阻塞函数(如getchar(), 会阻塞等待输入)
....
PS:中断的概念理解起来比较简单, 复杂的是通过中断实现消息驱动的过程, 但是幸运的是, 作为上层开发的我们, 不用关心底层怎么实现, 但是大致理解这个过程对我们的编程很有用
实现我们自己粗浅的消息驱动机制
复杂的事情不要非要往死里钻, 所有复杂的东西都是从最简单的开始, 所以理解 消息驱动机制
最简单的办法是抛开所有的理论, 创建一个最简单的模型, 然后不断往完善去拓展
最简单的事件驱动模型
int main(int arg, cont char* args[]){
bool handl_event = false;
while(!handl_event){
/// 一直等待事件 阻塞函数
void* event = wait_event();
////到这里说明有事件了, 处理, 同时内部决定要不要标记退出
handle_event(event, &handl_event);
}
return 0;
}
PS 上面是最简单的消息机制的模型 最少明确了3点:
main函数不能结束, 所以是一个
while循环
必须有一个能监测事件的函数, 如上面的
wait_event()
事件处理函数
handle_event()
理论上只能 单个处理, 接收了事件然后处理事件
分析 ---> wait_event()
是阻塞函数, 会阻塞main函数
通过return的方式将事件传递出来,而且返回的是void*, 虽然可以将所有的东西都抽象化, 但是对于编程来来说接口并不透明. 如果能专门定义一个事件的数据结构, 并且作为返回值的话, 那么接口的返回即实现了统一, 也相应的透明化了, 这里简单定义一下
typedef struct EventInfo* EventInfo;
struct EventInfo{
int type; ///事件类型
string name /// 事件名
void* info; ////其他的信息
};
至于怎么实现, 先不考虑, 但是理论上应该是调用系统的接口, 这样不会占用cpu的资源
分析 --> handle_event()
这一层面离我们开发的最近,接收到事件, 处理事件
多线程模拟的事件驱动, 代码如下:
#import <pthread.h>
#import <semaphore.h>
#include <unistd.h>
#include <queue>
#include <string>
#include <iostream>
using std::cout;
using std::string;
#define G_SEM_NAME "lbtest"
#pragma mark - 任务互斥锁
static pthread_mutex_t g_task_mutex;
static pthread_mutex_t g_work_mutex;
static sem_t* g_sem;
std::queue<std::string> g_queue;
#pragma mark - 初始化全局变量
void init_global(){
pthread_mutex_init(&g_task_mutex, NULL);
pthread_mutex_init(&g_work_mutex, NULL);
g_sem = sem_open(G_SEM_NAME, O_CREAT,0644,1);
}
#pragma mark - 释放全局变量
void destroy_global(){
pthread_mutex_destroy(&g_task_mutex);
pthread_mutex_destroy(&g_work_mutex);
sem_close(g_sem);
}
#pragma mark - 模拟内核等待任务
void* kernel_wait_task(void* arg){
char mem_buffer[20] = {};
while (1) {
pthread_mutex_lock(&g_task_mutex);
memset(mem_buffer, 0, 20);
std::cout << "输入任务:\n";
///会读取 \n
// fgets(mem_buffer, 20, stdin);
scanf("%s",mem_buffer);
g_queue.push(mem_buffer);
sem_post(g_sem);
///互斥解锁
pthread_mutex_unlock(&g_task_mutex);
}
return NULL;
}
#pragma mark - 模拟主线程工作
void* main_work(void* arg){
static int flag = 0;
do {
if (g_queue.size() == 0) {
sem_wait(g_sem);
if (g_queue.size() == 0)continue;
}
pthread_mutex_lock(&g_work_mutex);
string task = g_queue.front();
if (task == "stop") exit(0);
cout << "任务: " << task.data() << std::endl;
g_queue.pop();
pthread_mutex_unlock(&g_work_mutex);
} while (!flag);
return NULL;
}
#pragma mark - 模拟内核的初始化
pthread_t* kernel_init(){
static pthread_t kernel;
if (kernel == NULL)
pthread_create(&kernel, NULL, kernel_wait_task, NULL);
return &kernel;
}
#pragma mark - 模拟主线程
pthread_t* main_init(){
static pthread_t main;
if (main == NULL)
pthread_create(&main, NULL, main_work, NULL);
return &main;
}
void start(){
pthread_t* kernel = kernel_init();
pthread_t* main = main_init();
pthread_join(*kernel, NULL);
pthread_join(*main, NULL);
}
int main(int arg, char* const*argv){
init_global();
start();
destroy_global();
return 0;
}
解释:
根据
最基础模型
拓展出来kernel
线程模拟系统等待接收键盘输入事件
kernel
接收到事件后, 会发信号
main
线程模拟主线程工作, 同样是while
循环, 等待信号main
和kernel
2个线程里用的锁不一样, 原因是 这2个线程本来就是独立的.main
和kernel
若用同一把锁, 那么kernel线程
在发出信号后, 如果时间片
没有完毕, 会立即上锁, 导致main
不能获取锁, 而继续阻塞等待, 当然这不是主要原因, 主要原因是这2个线程本来就应该是独立的, 一个只管等待接收任务, 一个只管取任务执行用到了信号量控制并发, 所以打印的时候 2个线程会交错打印.
kernel
里的scanf
会阻塞整个进程, 即使并发也没用. 如果kernel
的每次loop后, 都sleep
一下, 那么接近百分百的几率是当前kernel
的时间片过期, 会立即并发main
, 然后加锁输出任务. 用2把锁在我的理解之内有线程安全的问题, 就是全局队列的add
(在kernel内)和pop
(在main内), 会不会出现抢断资源的情况, 最开始写的简单的队列就有这种malloc free
相关的错误, 但是换用了std的queue
,就没有这种问题了sem在mac os里不知道为什么, 并不能真正的控制并发, 知道的可以告诉我下, 虚心请教
iOS的消息驱动
iOS的消息驱动就是我们常常听到的大名鼎鼎的RunLoop
, 这套框架也是开源的, 不过是面向C层面
的, RunLoop
的实现很复杂, 本篇主要简述iOS
的驱动机制, 因个人能力有限, 不能深入探讨, 但是runloop的基本的东西都会涉及到 比如:RunLoop相关的数据结构
===> iOS启动的流程
===> 源码流程的部分解析
===> 创建自定义源
等等
先从main
函数看起
做iOS开发的都知道, iOS的入口函数是main
. 做过C语言开发的都在的, C的入口函数也是main
C--> main 无参数的
- 没有导入任何头文件, 也没有标准库的 stdio.h
int main(){
int a = 10;
}
采用gcc编译命令
gcc -o main.out main.m
最后编译成功, 说明了在不导入任何头文件
和不主动返回
的情况下都能顺利编译
- 导入了标准库的头文件
stdio.h
但是在main
里面没有用到任何与stdio.h
相关的函数
#include<stdio.h>
int main(void){
int a;
}
编译:
gcc main.m
也没有任何报错, 如果printf
也是可以编译通过的, 但如果引用了其他动态库的文件, 必须在编译的时候指定 mac上标准库里的不用指定
C--> main 有参数的
int main(int arg, char* args[]){
return 0;
/**
arg 表示参数的个数
args 表示每一个参数的值(字符串) 第一个参数是被占用的, 表示程序的的路径
运行这个控制台程序的时候, (unix) 在终端对应的目录下(假设已经编译成了可执行文件) ./a.out arg0 arg1
这样arg0, arg1就会传入 args里
*/
}
PS:上面介绍的和RunLoop
没有很直接的关系
iOS--> main 它保留了传统 main 有参的格式, 参数性质是一样的
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
和传统的控制台程序一样, 这里把所有的业务逻辑全封装到了UIApplicationMain
当中, 操作系统创建app这个进程的入口就是这个函数
iOS为了实现消息驱动机制, 死循环的代码封装在了 UIApplicationMain
当中
UIApplicationMain
- 参数1和参数2是我们点击了app后, 系统传入的参数, 打印结果:
int main(int argc, char * argv[]) {
for (int i = 0; i < argc; ++i) {
NSLog(@"%s",argv[i]);
}
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
///输出
2019-05-27 11:16:27.572264+0800 RunLoop[50480:4061596] /Users/liubo/Library/Developer/CoreSimulator/Devices/
77D35AD9-CDE1-4257-830F-2E923A9B8C79/
data/Containers/Bundle/Application/
2BE3C8E1-FD7F-47E2-BE57-46B6C2C0BB46/
RunLoop.app/RunLoop
查看苹果文档简单介绍下
UIApplicationMain
的参数意义
参数3
NSString*
,直译为主要的类名, 文档说这里传入的是应用程序类的名称, 必须是UIApplication
或者UIApplication的子类
系统会根据传入的字符串动态创建出单利参数4
NSString*
UIApplication对象会将系统级别的事件
委托给delegate
, 这里的NSString*
就是为UIApplication
提供Delegate
的类型, 断点在对应代理函数, 可以知道事件还是从 runloop出来的, 而且是source0
的事件
从现象开始探究
从上一篇 深入理解OC的运行时(Runtime) 可以知道oc
在运行之前会将所有的类的信息初始化到本地(类对象/元类对象)的一张表里, 所以, 进入main函数之前, 类的相关函数先被调用的
在AppDelegate
的load里断点相关的函数调用栈:
上图是没有进入
main
函数之前, 系统加载类对象的函数调用栈, 并没有runloop相关的信息(函数)
断点AppDelegate
的 application:didFinishLaunchingWithOptions:
明显的看到, 与
RunLoop
相关的函数已经被调用, 并且回调了比较显眼的函数__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
和
__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__
, 这些与runloop相关的函数
后面会根据源码介绍的
断点Appdelegate
的applicationWillResignActive:
app进入后台的时候调用
和刚启动比较, 前面的几个函数调用都一样, 并且也可以发现,
__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__
之前的基本一样, 基本可以断定已经进入循环了.
断点App禁止
的时候的调用栈 (点击 xcode 调速器区域的 暂停)
观察地址可以发现
mach_msg
调用了mach_msg_trap
, 这个mach_msg_trap
就是内核阻塞函数, 不占用CPU资源,mach_msg
在源代码里可以找到的, 下面继续看调用栈的函数:
虽然调用的地址在 并不是
mach_msg
的入口地址, 但是这里 注释表名里 实际上是调用到了mach_msg
, 上面的图是App禁止
的时候的调用栈, 说明了 一直停留在mach_msg
, 点击屏幕的时候可以自己测试, 我的测试:
__CFRunLoopRun 354行 call
0x102a3bcc4 <+1652>: callq 0x102a415b0 ; __CFRunLoopServiceMachPort
-> 0x102a3bcc9 <+1657>: cmpl $0x0, -0xc6c(%rbp)
点击屏幕后 259行 call
0x102a3bb1a <+1226>: callq 0x102a41370 ; __CFRunLoopDoSources0
-> 0x102a3bb1f <+1231>: movb %al, -0xc60(%rbp)
说明 点击屏幕后 调用栈回到 __CFRunLoopRun后, 执行了 0x102a3bcc9之后的某个位置的时候, 跳回去了, 说明了循环在__CFRunLoopRun之内
源代码分析
源代码可以在官网下载 CoreFoundation源码
从函数调用栈的角度去看, UIApplicationMain
调用了 GSEventRunModal
, 从lldb
的汇编可以看出, GSEventRunModal
实际调用的是CFRunLoopRunInMode
进入了CFRunLoopRunSpecific
, 观察CFRunLoopRunInMode
的声明
CFRunLoopRunInMode0.png
这个声明既可以在系统的头文件CFRunLoop.h
里找到, 也可以在源码中找到. 可以看出CFRunLoopRunInMode
的参数并没有CFRunLoopRef
对象, 只接收了模式
\runloop的过期时间
\处理事件后返回标记
, 说明GSEventRunModal
传给CFRunLoopRunInMode
的时候, 可能没有CFRunLoopRef
对象, 但是肯定内部肯定创建了一个模式mode
, 传递过去了, 观察GSEventRunModal
的汇编
GraphicsServices`GSEventRunModal:
0x11926c2bd <+0>: pushq %rbp
0x11926c2be <+1>: movq %rsp, %rbp
0x11926c2c1 <+4>: pushq %r14
0x11926c2c3 <+6>: pushq %rbx
0x11926c2c4 <+7>: movl %edi, %r14d
0x11926c2c7 <+10>: jmp 0x11926c31e ; <+97>
0x11926c2c9 <+12>: movq %rax, %rbx
0x11926c2cc <+15>: cmpb $0x0, 0xc6fd(%rip) ; __applicationPort + 3
0x11926c2d3 <+22>: je 0x11926c2ec ; <+47>
0x11926c2d5 <+24>: callq 0x119270cf6 ; symbol stub for: CFRunLoopGetCurrent
0x11926c2da <+29>: movq 0xc6f7(%rip), %rsi ; timingObserver
0x11926c2e1 <+36>: movq %rax, %rdi
0x11926c2e4 <+39>: movq %rbx, %rdx
0x11926c2e7 <+42>: callq 0x119270cea ; symbol stub for: CFRunLoopAddObserver
0x11926c2ec <+47>: xorl %esi, %esi
0x11926c2ee <+49>: movq %rbx, %rdi
0x11926c2f1 <+52>: movsd 0x5abf(%rip), %xmm0 ; FallbackTraitMatch.__FallbackTraitOrder + 104, xmm0 = mem[0],zero
0x11926c2f9 <+60>: callq 0x119270d08 ; symbol stub for: CFRunLoopRunInMode
-> 0x11926c2fe <+65>: cmpb $0x0, 0xc6cb(%rip) ; __applicationPort + 3
0x11926c305 <+72>: je 0x11926c31e ; <+97>
0x11926c307 <+74>: callq 0x119270cf6 ; symbol stub for: CFRunLoopGetCurrent
0x11926c30c <+79>: movq 0xc6c5(%rip), %rsi ; timingObserver
0x11926c313 <+86>: movq %rax, %rdi
0x11926c316 <+89>: movq %rbx, %rdx
0x11926c319 <+92>: callq 0x119270d02 ; symbol stub for: CFRunLoopRemoveObserver
0x11926c31e <+97>: testb %r14b, %r14b
0x11926c321 <+100>: je 0x11926c32c ; <+111>
0x11926c323 <+102>: cmpb $0x0, 0xc6b6(%rip) ; timingObserver + 7
0x11926c32a <+109>: jne 0x11926c385 ; <+200>
0x11926c32c <+111>: movq 0xc6b5(%rip), %rdi ; __runLoopModeStack
0x11926c333 <+118>: testq %rdi, %rdi
0x11926c336 <+121>: je 0x11926c366 ; <+169>
0x11926c338 <+123>: callq 0x119270bb8 ; symbol stub for: CFArrayGetCount
0x11926c33d <+128>: testq %rax, %rax
0x11926c340 <+131>: jle 0x11926c366 ; <+169>
0x11926c342 <+133>: movq 0xc69f(%rip), %rbx ; __runLoopModeStack
0x11926c349 <+140>: movq %rbx, %rdi
0x11926c34c <+143>: callq 0x119270bb8 ; symbol stub for: CFArrayGetCount
0x11926c351 <+148>: leaq -0x1(%rax), %rsi
0x11926c355 <+152>: movq %rbx, %rdi
0x11926c358 <+155>: callq 0x119270bca ; symbol stub for: CFArrayGetValueAtIndex
0x11926c35d <+160>: testq %rax, %rax
0x11926c360 <+163>: jne 0x11926c2c9 ; <+12>
0x11926c366 <+169>: movq 0x7ceb(%rip), %rax ; (void *)0x00000001126a19e0: __stderrp
0x11926c36d <+176>: movq (%rax), %rdi
0x11926c370 <+179>: leaq 0x64bd(%rip), %rsi ; "%s: NULL run loop mode. Exiting loop\n"
0x11926c377 <+186>: leaq 0x64dc(%rip), %rdx ; "GSEventRunModal"
0x11926c37e <+193>: xorl %eax, %eax
0x11926c380 <+195>: callq 0x1192710c2 ; symbol stub for: fprintf
0x11926c385 <+200>: movb $0x0, 0xc654(%rip) ; timingObserver + 7
0x11926c38c <+207>: popq %rbx
0x11926c38d <+208>: popq %r14
0x11926c38f <+210>: popq %rbp
0x11926c390 <+211>: retq
进入
GSEventRunModal
的栈帧后, 当前下一条要被执行的指令是停留在0x11926c2fe <+65>
, 而该函数从开始处第一条无条件跳转指令(0x11926c2c7 <+10>
) 跳转到了+97 == 0x11926c31e
, 根据汇编指令的走向,+97
之后, 只有一条指令会跳转回去(回到 +65之前 === 0x11926c360 <+163> 跳转回到+12
, 然后+12
这条指令根据字面意思是检查了app端口
, 如果是空的话, 又进行了跳转(跳转到了 + 47
), 中间省略了2个重要的函数CFRunLoopGetCurrent() 和 CFRunLoopAddObserver()
, 由于本人对汇编代码并不是很熟练, 所以并不能判断这里到底调用了这2个函数没有, 但是一定传递了一个mode
, 要不然没有什么意义. 现在我们打印一下, 主线程里所有的 模式, 打印只能在开启了runloop的情况下, 这里选择在viewDidLoad
里
CFArrayRef array = CFRunLoopCopyAllModes(CFRunLoopGetCurrent());
NSArray* oArray = (__bridge NSArray *)(array);
NSLog(@"%@",oArray);
///结果 并且全是字符串, 并不是 真正的mode的数据结构
<__NSArrayM 0x600003ac0f30>(
UITrackingRunLoopMode,
GSEventReceiveRunLoopMode,
kCFRunLoopDefaultMode,
kCFRunLoopCommonModes
)
直接打印runloop对象
<CFRunLoop 0x600002770100 [0x10a622ae8]>
{
wakeup port = 0xd03,
stopped = false,
ignoreWakeUps = false,
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x600001538c00 [0x10a622ae8]>
{
type = mutable set, count = 2,
entries =>
0 : <CFString 0x10d9f2070 [0x10a622ae8]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x10a634ed8 [0x10a622ae8]>{contents = "kCFRunLoopDefaultMode"}
},
common mode items = <CFBasicHash 0x600001538ab0 [0x10a622ae8]>
{
type = mutable set,
count = 15,
entries =>
1 : <CFRunLoopObserver 0x600002a743c0 [0x10a622ae8]>
{
valid = Yes,
activities = 0xa0,
repeats = Yes,
order = 2147483647,
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10d11d87d),
context = <CFArray 0x6000015715f0 [0x10a622ae8]>
{
type = mutable-small,
count = 1,
values = (
0 : <0x7ffc6f802048>
)
}
}
3 : <CFRunLoopObserver 0x600002a74280 [0x10a622ae8]>
{
valid = Yes,
activities = 0xa0,
repeats = Yes,
order = 2001000,
callout = _afterCACommitHandler (0x10d14b2af),
context = <CFRunLoopObserver context 0x7ffc6de01c30>
}
5 : <CFRunLoopSource 0x600002e7c0c0 [0x10a622ae8]>
{
signalled = No,
valid = Yes,
order = -1,
context = <CFRunLoopSource context>
{
version = 1,
info = 0x4d03,
callout = PurpleEventCallback (0x1129772c7)
}
}
12 : <CFRunLoopSource 0x600002e74540 [0x10a622ae8]>
{
signalled = No,
valid = Yes,
order = 0,
context = <CFRunLoopSource MIG Server>
{
port = 41987,
subsystem = 0x10d9aa500,
context = 0x600001b548a0
}
}
....
},
modes = <CFBasicHash 0x600001538bd0 [0x10a622ae8]>
{
type = mutable set,
count = 4,
entries =>
2 : <CFRunLoopMode 0x600002078340 [0x10a622ae8]>
{
name = UITrackingRunLoopMode,
port set = 0x1b07,
queue = 0x600003578e00,
source = 0x600003578f00 (not fired),
timer port = 0x2a07,
sources0 = <CFBasicHash 0x600001538900 [0x10a622ae8]>
{
type = mutable set,
count = 4,
entries =>
0 : <CFRunLoopSource 0x600002e7c000 [0x10a622ae8]>
{
上述 CFRunLoopSource的形式结构
}
...
},
sources1 = <CFBasicHash 0x6000015388d0 [0x10a622ae8]>
{
type = mutable set,
count = 3,
entries =>
{
上述sources0的结构
}
...
},
observers = (
各种observer的数据结构, 和上面一样, 但是这是字符串
),
timers = (null),
currently 581307132 (277149296714446) / soft deadline in: 1.84464669e+10 sec (@ -1) / hard deadline in: 1.84464669e+10 sec (@ -1)
},
3 : <CFRunLoopMode 0x6000020784e0 [0x10a622ae8]>
{
name = GSEventReceiveRunLoopMode,
port set = 0x2e03,
queue = 0x600003579080,
source = 0x600003579180 (not fired),
timer port = 0x4f03,
和上述CFRunLoopMode相同的结构
},
4 : <CFRunLoopMode 0x600002078270 [0x10a622ae8]>
{
name = kCFRunLoopDefaultMode,
port set = 0x1303,
queue = 0x600003578800,
source = 0x600003578780 (not fired),
timer port = 0xf03,
source0 和上面一样
source1 和上面一样
observer 和上面一样
timers = <CFArray 0x600003f74180 [0x10a622ae8]>
{
type = mutable-small,
count = 2,
values = (
0 : <CFRunLoopTimer 0x600002e7c480 [0x10a622ae8]>
{
valid = Yes,
firing = No,
interval = 0.5,
tolerance = 0,
next fire date = 581307132 (0.480407 @ 277149781817356),
callout = (NSTimer) [UITextSelectionView caretBlinkTimerFired:] (0x1094430e2 / 0x1091327af) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore),
context = <CFRunLoopTimer context 0x600001b69580>
},
....
)
},
currently 581307132 (277149298374434) / soft deadline in: 0.483442903 sec (@ 277149781817356) / hard deadline in: 0.483442842 sec (@ 277149781817356)
},
5 : <CFRunLoopMode 0x60000207c340 [0x10a622ae8]>
{
name = kCFRunLoopCommonModes,
port set = 0x380b,
queue = 0x60000357d800,
source = 0x60000357d900 (not fired),
timer port = 0x300f,
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),
currently 581307132 (277149302507943) / soft deadline in: 1.84464669e+10 sec (@ -1) / hard deadline in: 1.84464669e+10 sec (@ -1)
},
}
}
从上面
runloop
的打印, 根据数据, 整理下相关的数据结构
CFRunLoop
struct CFRunLoop{
//整型 端口, 被内核唤醒 (猜测)
unsigned long long wakeup port;
//猜测是当前的状态
bool stopped;
//被唤醒的时候 不能忽略 (字面意思)
bool ignoreWakeUps;
//当前runloop的模式
string current_mode
//被标记为 common的 mode
CFMutableSetRef common_modes;
//标记为 common的 源(source/timer/observer)
CFMutableSetRef common_mode_items;
/*
当前runloop所有的mode 基本上是
UITrackingRunLoopMode
kCFRunLoopCommonModes
kCFRunLoopDefaultMode
GSEventReceiveRunLoopMode
公开的 就前3个
*/
CFMutableSetRef modes;
};
可以看出
runloop
对象里, 记录的 都是mode
, 执行的任务是以模式为单位current_mode
, 猜测当外界, 例如上面探讨的函数调用栈里GSEventRunModal
指定的一个名为GSEventReceiveRunLoopMode
后, 就会去modes
里找到对应的mode
, 然后取出其中的源处理
CFRunLoopMode
struct CFRunLoopMode{
//名字 对应4个模式名
string name;
//端口号
unsigned long long port;
//队列 dispath 队列
void* queue;
//set 里面放的是 source
CFMutableSetRef sources0;
CFMutableSetRef sources1;
//array 里面放的是timer
CFMutableArrayRef timers;
//array 里面放的是observer
CFMutableArrayRef observers;
...
};
可以看出 mode 里全是事件源
CFRunLoopSource (虽然有source0和source1, 但都是这样的结构)
struct CFRunLoopSource{
///直接说结论, 标记源, 这样runloop被唤醒的时候才会执行被标记的源
bool signalled;
///源是否有效
bool valid;
///应该是多个源的时候, 处理的优先级, 类比CFRunloopObserver的 order
int order;
///这个应该是 源相关的 函数指针的回调 和 参数数据, runloop执行的时候, 最终取到的是这里, 猜测
void* context;
...
};
CFRunLoopObserver
struct CFRunLoopObserver{
//是否有效
bool valid;
//监听的状态
long long activities;
//是否重复监听
bool repeats;
//多个observer监听时, 优先级
uint64_t order;
//函数指针 系统注册的几个observer里有关于 autoreleasePool的操作
void* callout;
//上下文, 可能是数组, 数组里面应该是注册observer的回调地址
void* context;
....
};
观察系统 注册的observer, 可以看到有几个关于
autoreleasePool
的监听操作, 监听的状态是(CFRunLoopActivity
) 0xa0(0b10100000
)和0x0, 对应的状态是(kCFRunLoopEntry kCFRunLoopBeforeWaiting 和 kCFRunLoopExit
), 猜测应该是进入runloop的时候, 创建自动释放池, 退出runloop的时候释放掉, 并且每次 睡眠的时候, 会对释放池做操作, 具体是什么操作不是很清楚
CFRunLoopTimer
struct CFRunLoopTimer{
//定时器是否有效
bool valid;
//是否正在执行
bool firing;
//执行的间隔时间
NSTimeInterval interval;
//延迟
NSTimeInterval tolerance;
//回调地址
void* callout;
//回调的信息
void* context;
...
};
相关源代码分析
CFRunLoopRunInMode
- 从函数调用栈里可以看出, 系统
启动循环
的时候, 从这里开始的, 注意是启动
, 并不意味着创建
, 可能这个时候应创建好了runloop
, 也可能没有创建, 前面分析的时候, 也是说了, 根据App端口
是否空, 决定要不要执行CFRunLoopGetCurrent 和 CFRunLoopAddObserver
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
/// 先获取runloop 对象 然后切换模式
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
可以看到, 这里被调用
CFRunLoopRunSpecific
并不是公开的接口, 而且全局搜索当前的CFRunLoop.c
,只有2个地方调用了CFRunLoopRunSpecific
, 一个是公开的接口本函数, 另一个也是公开的接口CFRunLoopRun
, 只是CFRunLoopRun
的参数是强制性传的 指定的模式
CFRunLoopRun.png
这要说明一点, 当前这些调用, 并没有看到 最简单模式的while
CFRunLoopGetCurrent()
#pragma mark - 获取当前的runloop
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
///先从缓存取, 取到 直接返回
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
///创建runloop
return _CFRunLoopGet0(pthread_self());
}
- 可以看到, 当前函数先去取, 取到了直接返回, 否则就调用
_CFRunLoopGet0
返回, 明显的将 当前线程 传递了过去, 上面取runloop
的操作也说明了runloop
是当前线程
的私有数据(TSD), 紧接着_CFRunLoopGet0()
函数
pragma mark - 全局的runloop字典
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFSpinLock_t loopsLock = CFSpinLockInit;
// should only be called by Foundation 只能当前.c文件里私有调用
// t==0 is a synonym for "main thread" that always works t==0就相当于传主线程
#pragma mark - 根据线程获取 runloop对象
#pragma mark - 根据线程获取 runloop对象
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
//这个if就应证了上面说的 t == 0
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
//如果全局的 runloop的字典是空的, 这里是线程锁定访问这个全局变量
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {
__CFSpinUnlock(&loopsLock);
//创建可变的 字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//创建主线程的 runloop对象, 因为字典是空的, 表示没有runloop对象,也表示了主线程的runloop是最早创建的
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
//将线程(main) 和 runloop 对应起来存入到字典里
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
// 虽然不知道函数的具体作用, 但是猜测应该是 线程安全设置__CFRunLoops, 相当于retain了一次, 毕竟后面release了
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFSpinLock(&loopsLock);
}
//上面是初始化全局字典的时候, 这里可能是多次进来之后, 所以直接根据 线程 去字典里找 对应的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
/*
如果 没有从字典里取到 对应的 runloop
要注意这里没加锁的原因, 我猜测的是 当前是某一条线程t 正在执行
而 loop 是一个局部变量 加锁没有意义上,线程里操作 都是按顺序执行的
没有线程抢断的概念在这里
*/
if (!loop) {
/*
根据 当前线程 去创建 一个新的 runloop对象
考虑到多线程, 这会创建很多次, 但是本质上没有冲突, 因为是局部变量, 都是独立的
*/
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
/*
这里加锁 不是很懂意义, 但是可以明显的知道, 对全局变量的访问都加锁了
我不懂的地方是, 这里的 CFDictionaryGetValue获取 局部loop的时候, 线程明明都是 独立的, 为什么getvalue的时候要加锁
我思考的原因可能是 数据结构字典(oc里应该是红黑树), 访问和设置应该是原子操作, 访问的时候, 不应该正在插入值
红黑树在插入值的时候, 做平衡调整的时候会调整树的 "形状", 我想这个过程不应该 getvalue, 当然这只是我猜想, 有时间我会研究数据结构的
*/
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
//设置值到字典里
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
//这个是苹果 原文提醒, 警告我们, 不要在锁中 释放掉newLoop, 因为CFRunLoopDeallocate可能最终要接受它, 并不是很懂
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFSpinUnlock(&loopsLock);
CFRelease(newLoop);
}
/*
上面介绍过一个函数CFRunLoopGetCurrent(), 这个函数在获取当前线程的runloop对象的时候, 是先根据TSD获取, 没获取到才创建runloop对象
目前为止, 创建runloop,设置runloop一直没有将runloop加入到 线程的私有数据(TSD)的操作, 下面的if就是
但是 貌似获取用的参数不一致 (不懂) __CFTSDKeyRunLoopCntr 和 __CFTSDKeyRunLoop
*/
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
上面一系列流程实际上只是 CFRunLoopRunInMode
传递给CFRunLoopRunSpecific()
第一个参数CFRunLoopGetCurrent()
, 其中涉及到RunLoop
的创建(__CFRunLoopCreate(pthread_t)
), 在看__CFRunLoopCreate
之前, 先来看看与RunLoop
相关的数据结构(_per_run_data
, _per_run_data
, CFRuntimeBase
, _CFRuntimeCreateInstance
, __CFRunLoopMode
)和相关的其他宏
struct __CFRunLoop 结构
typedef struct _per_run_data {
uint32_t a;
uint32_t b;
uint32_t stopped;
uint32_t ignoreWakeUps;
} _per_run_data;
struct _block_item {
struct _block_item *_next;
CFTypeRef _mode; // CFString or CFSet
void (^_block)(void);
};
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; ////锁定访问模式
__CFPort _wakeUpPort; ////用于runloop唤醒 端口
Boolean _unused;
// 重置运行循环,这个是苹果的翻译解释, 记录了runloop相关的状态
// 涉及到创建的函数 __CFRunLoopPushPerRunDat, 苹果说了一些bit位:
//第0位bit位表示stopped 第1位bit位表示sleeping 第2位bit位表示deallocating
volatile _per_run_data *_perRunData;
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes; /// mset 所有的被标记为 common的 模式列表
CFMutableSetRef _commonModeItems; /// mset 所有的被标记为 common的 源(source0\source1\timer\observer)
CFRunLoopModeRef _currentMode; /// mode 当前正在运行的模式
CFMutableSetRef _modes; /// mset 所有的模式 包含了 非common的mode的list
struct _block_item *_blocks_head; //block放在了这里
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
和之前根据打印结果总结的数据结构还是有些偏差的,这里要注意runloop
的结构里有2个block链表
(_blocks_head
和 _blocks_tail
),说明RunLoop
被唤醒后, 除了处理源(source0,source1,timer)
,还处理block
CFRuntimeBase
这个结构可以在
CFRuntime.h
找到, 网上介绍这个结构的作用的文章几乎没有, 找到一篇相关的
__CFRuntimeBase的研究
/*
头文件很明确的告诉我们, 这个结构是所有 "CF" 结构的开始
也就是说 CF 定义的结构, 最开始都是 以 __CFRuntimeBase开始
不要直接引用这些字段, 这些字段是供CF使用的,
CF中引用这些字段的方法只有通过函数调用__CFBitfieldGetValue 和 __CFBitfieldSetValue
*/
typedef struct __CFRuntimeBase {
uintptr_t _cfisa;
uint8_t _cfinfo[4];
#if __LP64__
uint32_t _rc;
#endif
} CFRuntimeBase;
_CFRuntimeCreateInstance
CF_EXPORT CFTypeRef _CFRuntimeCreateInstance(CFAllocatorRef allocator, CFTypeID typeID, CFIndex extraBytes, unsigned char *category);
使用给定的分配器创建由给定CFTypeID指定的类的新CF实例,并返回它。
如果分配器返回NULL,则此函数返回NULL。
在返回实例的开始处初始化
CFRuntimeBase
结构, 前面讲了所有的CF结构
的第一个成员都是CFRuntimeBase
, 创建CF
实例的时候, 底部会调用这个函数, 并且内部会自动初始化CFRuntimeBase
的部分额外字节是为实例分配的额外字节数(超出CFRuntimeBase所需的字节数)。实际就是 要创建
CF
实体中出去CFRuntimeBase
部分的大小如果CF运行时不知道指定的CFTypeID,则此函数返回NULL。
除了基头之外,新内存的任何部分都不会初始化(例如,额外的字节不为零)。也就是说 函数只会对
CFRuntimeBase
的部分初始化, 其他部分不会初始化使用这个函数创建的所有实例只能通过使用CFRelease()函数来销毁——即使在类的初始化或创建函数中,也不能直接使用CFAllocatorDeallocate()来销毁实例。为类别参数传递NULL。
上面介绍了 对__CFRuntimeBase
的操作只有通过函数 __CFBitfieldGetValue
和 __CFBitfieldSetValue
的说法是不正确的, 其实他们是定义在CFInternal.h
下的2个宏
#define __CFBitfieldMask(N1, N2) ((((UInt32)~0UL) << (31UL - (N1) + (N2))) >> (31UL - N1))
#define __CFBitfieldGetValue(V, N1, N2) (((V) & __CFBitfieldMask(N1, N2)) >> (N2))
#define __CFBitfieldSetValue(V, N1, N2, X) ((V) = ((V) & ~__CFBitfieldMask(N1, N2)) | (((X) << (N2)) & __CFBitfieldMask(N1, N2)))
来看具体的调用例子
typedef struct __CFRuntimeBase {
uintptr_t _cfisa;
uint8_t _cfinfo[4];
#if __LP64__
uint32_t _rc;
#endif
} CFRuntimeBase;
CF_INLINE Boolean __CFRunLoopIsSleeping(CFRunLoopRef rl) {
return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 1, 1);
}
//化简之后
__CFBitfieldGetValue( _cfinfo[idx],1,1) //_cfinfo里存放的是 unsigned char 数组, 这里取出了其中一个 假设是value
__CFBitfieldGetValue( value,1,1)
//先展开 __CFBitfieldMask
( ( ( (UInt32) ~ 0UL ) << (31UL - (1) + (1) ) ) >> (31UL - 1) )
分析这个宏, 如果是64位的cpu 先将 0xFF FF FF FF FF FF FF FF, 强制转换成32位 0xFF FF FF FF
然后左移 31位,变成了 0x80 00 00 00, 最后右移30位 == 0x00 00 00 02, 所以这宏的值就是 2
value传递的是 8位的数据, 只和 0x02 与操作很明显 目的是取出第1位的bit值, 说明runloop的sleep的状态位是bit=1
而且后面 介绍runloop的_perRunData的时候, 头文件里也很明确的说明了 sleep的bit位是1
( ( (value) & 0x00 00 00 02 ) >> (1) )
设置对应的bit位
CF_INLINE void __CFRunLoopSetSleeping(CFRunLoopRef rl) {
__CFBitfieldSetValue(((CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 1, 1, 1);
}
//runloop的结构里的状态是一个_per_run_data的指针, 文档虽然数第0位stopped, 第1位sleeping, 第2位deallocating, 但是这里却是对runloop对象的 __CFRuntimeBase部分取值赋值, 那究竟和_per_run_data有什么关系呢?
struct __CFRunLoopMode 结构
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; //对应几个模式的名字
Boolean _stopped; //mode的状态
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers; //这个里面是NSTimer的集合
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
///为什么有这个定时器?根据源码的结果去看貌似唤醒后是处理timers的 平台下定时的不同结构
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
了解了上述结构, 接下来看看 __CFRunLoopCreate
这个函数全局只被
_CFRunLoopGet0
调用,也就是说只是在获取runloop
的时候才会被创建
#pragma mark - 根据线程id创建runloop
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
//局部变量 runloop对象
CFRunLoopRef loop = NULL;
//局部变量 模式mode
CFRunLoopModeRef rlm;
/* 创建 当前线程 t 对应的 loop 对象
*/
uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL);
if (NULL == loop) {
return NULL;
}
/*
创建并初始化 loop 的 _perRunData部分
__CFRunLoopPushPerRunData 主要是创建新的_perRunData, 返回之前的_perRunData
这个字段 里面都是 32的bit, 用来表示 状态相关的信息, 函数内部初始化的时候相关的值
a = 0x4346524C; 0b 0100 0011 0100 0110 0101 0010 0100 1100
b = 0x4346524C; 0b 0100 0011 0100 0110 0101 0010 0100 1100 // 'CFRL'
stopped = 0x00000000; 0b 0000 0000 0000 0000 0000 0000 0000 0000
ignoreWakeUps = 0x00000000; 0b 0000 0000 0000 0000 0000 0000 0000 0000
*/
(void)__CFRunLoopPushPerRunData(loop);
//每个runloop结构里都有自己的锁, 这里是初始化 当前创建的runloop的锁
__CFRunLoopLockInit(&loop->_lock);
//从字面意思可以看出, 创建了唤醒runloop的端口
loop->_wakeUpPort = __CFPortAllocate();
if (CFPORT_NULL == loop->_wakeUpPort) HALT;
/*
开始设置runloop的相关的状态标记 函数注释是 wake
ignoreWakeUps = 0x57414B45 0b 0101 0111 0100 0001 0100 1011 0100 0101
*/
__CFRunLoopSetIgnoreWakeUps(loop);
//创建runloop commoMode的集合
loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
/*
最开始打印runloop的时候, 打印的结果的runloop里有 commonModes的集合,
里面放的是 模式, 字符串 eg:
{
common modes = <CFBasicHash 0x600001538c00 [0x10a622ae8]>
{
type = mutable set, count = 2,
entries =>
0 : <CFString 0x10d9f2070 [0x10a622ae8]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x10a634ed8 [0x10a622ae8]>{contents = "kCFRunLoopDefaultMode"}
},
...
}
*/
CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
//因为是新建的 runloop, 并没有添加任何的源, 所以其他的字段基本是空
loop->_commonModeItems = NULL;
loop->_currentMode = NULL;
loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
loop->_blocks_head = NULL;
loop->_blocks_tail = NULL;
loop->_counterpart = NULL;
loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
loop->_winthread = GetCurrentThreadId();
#else
loop->_winthread = 0;
#endif
/*
创建runloop目前为止, 并没有创建相关的源
这个函数内部会去通过 第2个参数模式名 去 runloop的modes中找匹配的mode
第3个参数 如果发现没找到, 会主动创建
注意 是去modes里找, 很明显 当前runloop的 modes肯定是没有任何模式的,
这里会创建 kCFRunLoopDefaultMode 的 mode, 但是不会创建任何源,会根据不同平台创建 定时器(不是源)
*/
rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
return loop;
}
通过观察源码, 发现创建一个新的runloop
对象的时候
初始化了标记状态的部分
_perRunData
唤醒的端口_wakeUpPort
创建了_commonModes
(字符串common模式列表), 并添加了kCFRunLoopDefaultMode
创建了 模式列表(_modes
), 但是没有添加任何的源
设置runloop
的状态是 wake
调用__CFRunLoopFindMode
主动创建kCFRunLoopDefaultMode
的mode
, 第三个参数代表找不到要创建,这里的确是要创建
__CFRunLoopFindMode
/* call with rl locked, returns mode locked */
#pragma mark - 到指定的 runloop对象里根据name寻找 mode
static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create) {
/** 进程检查相关*/
CHECK_FOR_FORK();
/**
struct __CFRunloopMode *
*/
CFRunLoopModeRef rlm;
/**
创建临时的 __CFRunloopMode, 并初始化, 主要是用来到 runloop对象里遍历 modes, 根据 name 找出 rl中的 mode
*/
struct __CFRunLoopMode srlm;
memset(&srlm, 0, sizeof(srlm));
_CFRuntimeSetInstanceTypeIDAndIsa(&srlm, __kCFRunLoopModeTypeID);
srlm._name = modeName;
rlm = (CFRunLoopModeRef)CFSetGetValue(rl->_modes, &srlm);
///如果找到了 解锁直接返回
if (NULL != rlm) {
__CFRunLoopModeLock(rlm);
return rlm;
}
///如果没有找到, 并且外界指定不要创建, 直接返回 空, 这个时候 还是锁住的
if (!create) {
return NULL;
}
///准备创建 返回的 mode
rlm = (CFRunLoopModeRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopModeTypeID, sizeof(struct __CFRunLoopMode) - sizeof(CFRuntimeBase), NULL);
if (NULL == rlm) {
return NULL;
}
////创建好后 初始化操作
__CFRunLoopLockInit(&rlm->_lock);
/// mode的 名字
rlm->_name = CFStringCreateCopy(kCFAllocatorSystemDefault, modeName);
/// mode 的状态位不停止
rlm->_stopped = false;
///字典
rlm->_portToV1SourceMap = NULL;
///mulset
rlm->_sources0 = NULL;
///mulset
rlm->_sources1 = NULL;
///mulArray
rlm->_observers = NULL;
///mulArray
rlm->_timers = NULL;
///标记监听的状态是kCFRunLoopEntry, 注意不是被唤醒
rlm->_observerMask = 0;
/// 在window下是一个 port的结构体指针, 在mac下是一个uint32 整型
rlm->_portSet = __CFPortSetAllocate();
/// 字面意思 软的 定时器 的截止时间
rlm->_timerSoftDeadline = UINT64_MAX;
/// 字面意思 硬的 定时器 的截止时间
rlm->_timerHardDeadline = UINT64_MAX;
///int KERN_SUCCESS上面宏定义0
kern_return_t ret = KERN_SUCCESS;
/// 不同平台下创建定时器源 具体这些定时器做什么工作目前还不知道
//如果当前是mac osx 这个值和 USE_MK_TIMER_TOO 都是1
#if USE_DISPATCH_SOURCE_FOR_TIMERS
///定时器不开启
rlm->_timerFired = false;
/**
每个模式下有自己的队列, 具体这个队列有什么用, 目前为止还不是很清楚
这里和我们平时创建 GCD队列不同, 但是效果应该是一样的
*/
rlm->_queue = _dispatch_runloop_root_queue_create_4CF("Run Loop Mode Queue", 0);
/**
和平时我们创建 GCDtimer 不一样, 这里多了一个步骤
根据上面创建的队列获取对应的端口 说明了创建rml->_queue的时候, 可能已经对应的创建了端口
说可能是因为 _dispatch_runloop_root_queue_get_port_4CF 会懒加载创建 port
*/
mach_port_t queuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (queuePort == MACH_PORT_NULL) CRASH("*** Unable to create run loop mode queue port. (%d) ***", -1);
/// 创建gcd定时器源
rlm->_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, rlm->_queue);
/// 设置定时器的回调, 主要任务是设置 当前mode的_timerFired为true
__block Boolean *timerFiredPointer = &(rlm->_timerFired);
dispatch_source_set_event_handler(rlm->_timerSource, ^{
*timerFiredPointer = true;
});
///这里将定时器间隔时间设置到了 无限远, 理论上意味着永远达不到执行的时间点
// Set timer to far out there. The unique leeway makes this timer easy to spot in debug output.
_dispatch_source_set_runloop_timer_4CF(rlm->_timerSource, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 321);
dispatch_resume(rlm->_timerSource);
/**
这句代码在 核心循环的的函数里 出现过对应的get, 应该是调用这句代码, 对应的端口才会被监听
具体看 run 函数里的分析
*/
ret = __CFPortSetInsert(queuePort, rlm->_portSet);
if (KERN_SUCCESS != ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);
#endif
#if USE_MK_TIMER_TOO
rlm->_timerPort = mk_timer_create();
ret = __CFPortSetInsert(rlm->_timerPort, rlm->_portSet);
if (KERN_SUCCESS != ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);
#endif
ret = __CFPortSetInsert(rl->_wakeUpPort, rlm->_portSet);
if (KERN_SUCCESS != ret) CRASH("*** Unable to insert wake up port into port set. (%d) ***", ret);
#if DEPLOYMENT_TARGET_WINDOWS
rlm->_msgQMask = 0;
rlm->_msgPump = NULL;
#endif
/// 对于新建的 mode, 加入到当前 runloop的modes的列表里
CFSetAddValue(rl->_modes, rlm);
/// 当前函数解 引用
CFRelease(rlm);
__CFRunLoopModeLock(rlm); /* return mode locked */
return rlm;
}
上面是 在通过
ModeName
到runLoop
对象去匹配 mode的时候,可能会创建对应模式的mode
在函数调用栈CFRunLoopRunInMode
调用CFRunLoopRunSpecific
时, 传递第一个参数CFRunLoopGetCurrent()
做了大致以上的工作, 接下来看CFRunLoopRunSpecific
CFRunLoopRunSpecific
需要先说明一点,
CFRunLoopRunSpecific
函数是只被2个函数调用(CFRunLoopRun()
和CFRunLoopRunInMode()
), 并且这2个函数是公开的API, 此函数的作用是运行 当前runloop
的指定模式名的 模式
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl,
CFStringRef modeName,
CFTimeInterval seconds,
Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
/**
检查进程相关的
*/
CHECK_FOR_FORK();
/**
Deallocating的bit位是2, __CFRunLoopPushPerRunData函数上面标注的
而且前面介绍 __CFRuntimeBase 相关的2个宏的时候, 分析过 sleep(bit位是1)
这里查看runloop对象的状态 如果正在析构, 直接返回结束
*/
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
/**
将rl 上锁访问
*/
__CFRunLoopLock(rl);
/**
根据 name 到 runloop中寻找要切换的 mode, 也就是接下来要启动的模式
这里去runloop中 根据 modeName 去 模式列表里匹配
第三个参数 表示不创建 mode
*/
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
/**
如果没有找到, 不启动
如果找到,但是 __CFRunLoopModeIsEmpty函数返回空 不启动
__CFRunLoopModeIsEmpty并不是简单的判断runloop的模式列表里的源是不是空
具体请看函数__CFRunLoopModeIsEmpty
*/
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
/// 这里不是很明显吗 返回kCFRunLoopRunFinished
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
///这里 会去创建 runloop的新的_per_run_data, 然后将之前的返回
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
/**
根据字面意思 当前 rl执行的模式是_currentMode 而本函数的作用是根据 name 启动 runloop 对应的模式,
上面的局部变量currentMode 肯定是能从 rl的 modes 中取出来的, 因为find的时候传入了 false(表示不创建 mode),
所以 现在要指定新的模式, 那么当前 rl->_currentMode 就是所谓的 previousMode 了
可以对比我们创建 default的timer, 在准备滑动scroll的时候, default模式优先级没有Tracking模式高, 所以default就相当于上一个
tracking相当于接下来要启动的模式
*/
CFRunLoopModeRef previousMode = rl->_currentMode;
/// 切换 rl的_currentMode 为指定的 currentMode
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
/// 查看取出模式 的 runloop监听的状态, 如果是 刚进入 kCFRunLoopEntry, 就通知所有的 监听者
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
/// 开始启动这个 mode的 runloop, 下面就进入 while循环了
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
////上面循环结束后, 检查当前运行的mode的状态, 如果是退出, 就通知 所有的 监听者
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
///还原之前的状态
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
上面的函数是根据ModeName
运行对应的模式,观察源代码可以发现, 并没有进入所谓的循环模式, 同时只通知了观察者kCFRunLoopEntry
和 kCFRunLoopExit
, do while
被恰在了中间, 也就是说, 对一个runloop
run的时候, 最开始收到的通知是kCFRunLoopEntry
, 如果runloop
启动后, 再次被唤醒的时候, 是不会再收到kCFRunLoopEntry
, runloop
停掉后才会收到kCFRunLoopExit
, 接下来看看这个函数里有关的函数__CFRunLoopModeIsEmpty
和__CFRunLoopDoObservers
__CFRunLoopModeIsEmpty
// expects rl and rlm locked
#pragma mark - 判断runloop的mode是不是空
static Boolean __CFRunLoopModeIsEmpty(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopModeRef previousMode) {
CHECK_FOR_FORK();
//如果传进来的 当前的mode是空, 直接返回
if (NULL == rlm) return true;
#if DEPLOYMENT_TARGET_WINDOWS
if (0 != rlm->_msgQMask) return false;
#endif
bool isMainThread = pthread_main_np();
bool tflag0 = HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY; //false
/*
获取相关的 GCDMainQ的线程私有数据
从字面理解是GCD相关的, 后面在runloop的唤醒后处理端口的逻辑代码里可以 推断出这里返回的是端口号
如果返回了端口号, 表示GCD主队列有 事件, 当然这只是我的推断
*/
bool tflag1 = (0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ));
//这里的条件表示 如果是主线程,那么只看 tflag1 是不是true
Boolean libdispatchQSafe = isMainThread && ( (tflag0 && NULL == previousMode)/*false*/ || (!tflag0 && tflag1) );
/*
如果是主线程
并且没有获取到 GCDMainQ的数据
并且当前要 loop 的模式是 common标记的模式
直接告诉外界 此时runloop的模式列表modes不空
*/
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) return false; // represents the libdispatch main queue
/*
如果相关的 各种模式的集合 已经分配了空间, 但是缺没有源 也告诉外界 runloop的模式列表不是空的
这里明显有优先级的判断: _sources0 > _sources1 > _timers
是不是说 在被唤醒后处理源的时候, 也是按 这个顺序去处理源?
*/
if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) return false;
if (NULL != rlm->_sources1 && 0 < CFSetGetCount(rlm->_sources1)) return false;
if (NULL != rlm->_timers && 0 < CFArrayGetCount(rlm->_timers)) return false;
//runloop的结构里有2个 block 的链表, 一个叫_blocks_head, 一个叫_blocks_tail, 在创建runloop的时候(__CFRunLoopCreate),是NULL
//但是这里可能有值, 说明当前函数在被其他函数调用的时候, runloop的_blocks_head可能有值
//也说明了 block的回调是放在这个链表里的, 也作为runloop处理的事件
struct _block_item *item = rl->_blocks_head;
/*
runloop结构里的 block结构
struct _block_item {
struct _block_item *_next;
CFTypeRef _mode; // CFString or CFSet
void (^_block)(void);
};
1 这里的循环是一次取出 每一个block
2 取出的block可能是 string 也可能是set 所以, while里有判断
3 如果 当前取出的 block的模式 == 当前runloop要运行模式(mode), 可能是default或者common等, 就标记为要处理
而且只要发现有一个要被标记了, 就告诉外界 当前runloop的模式列表不空(return false), 其实就意味着有事件要处理
*/
while (item) {
struct _block_item *curr = item;
item = item->_next;
Boolean doit = false;
if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
doit = CFEqual(curr->_mode, rlm->_name) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
} else {
doit = CFSetContainsValue((CFSetRef)curr->_mode, rlm->_name) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
}
//如果 需要处理(doit = true), 表示runloop的modes不空, 要启动处理block
if (doit) return false;
}
return true;
}
__CFRunLoopDoObservers
要分析这个函数, 先看下数据结构
__CFRunLoopObserver
__CFRunLoopObserver
//定义在RunLoop.h里 也就是公开的结构
typedef struct {
CFIndex version;
void * info; //作为ob回调时, cf调用CFRunLoopObserverCallBack的参数info
const void *(*retain)(const void *info); //如果设置了这个值, 那么最后回调的时候传的是这个, 即使info有值
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
} CFRunLoopObserverContext;
//定义在RunLoop.h里 也就是公开的结构
typedef void (*CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
//定义在RunLoop.c里, 没有公开
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock; //线程锁
CFRunLoopRef _runLoop; //率属于哪个runLoop, 说明了, observer只能同时监听一个runloop对象
CFIndex _rlCount; //猜一下应该是被引用的次数,因为ob可用放在不同的模式里, 在创建ob的时候会详细分析
CFOptionFlags _activities; //注册监听runLoop的状态, 这大家都懂
CFIndex _order; //多个observer的时候, 回调的优先级
CFRunLoopObserverCallBack _callout; //回调函数
CFRunLoopObserverContext _context; //回调函数的参数信息
};
__CFRunLoopDoObservers
static void __CFRunLoopDoObservers(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopActivity activity) { /* DOES CALLOUT */
CHECK_FOR_FORK();
//如果模式里没有监听者, 直接不通知
CFIndex cnt = rlm->_observers ? CFArrayGetCount(rlm->_observers) : 0;
if (cnt < 1) return;
/* Fire the observers */ //通知监听者的操作
/*
STACK_BUFFER_DECL 是定义在 CFInternal.h的宏,虽然是根据平台定义的,但是 本质是上开辟了缓冲区
这里相当于是 CFRunLoopObserverRef buffer[cnt]
如果当前传入的模式下的监听者的个数 > 1024
比如是 2000, 那么缓冲区的大小就是 CFRunLoopObserverRef * 1
否则 缓冲区大小是 CFRunLoopObserverRef * cnt
在我们印象里, C语言里数组的定义的时候, 数组下标的指定不能用局部变量, 所以这里为什么这样,我也不是很懂
这里宏 区分了window的平台, 用了特有的函数去调用
*/
STACK_BUFFER_DECL(CFRunLoopObserverRef, buffer, (cnt <= 1024) ? cnt : 1);
/*
注意这里定义的变量 类型是 动态分布的
如果 监听者的个数 < 1024, 那么就不malloc
否则 collectedObservers的长度是 cnt(eg:2000) * sizeof(CFRunLoopObserverRef)
*/
CFRunLoopObserverRef *collectedObservers = (cnt <= 1024) ? buffer : (CFRunLoopObserverRef *)malloc(cnt * sizeof(CFRunLoopObserverRef));
CFIndex obs_cnt = 0;
//变量所有的监听者
for (CFIndex idx = 0; idx < cnt; idx++) {
//从列表(CFMultablArrayRef)里取出 监听者rlo
CFRunLoopObserverRef rlo = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
/*
如果取出来的监听者rlo有 对 目标activity的监听
并且 其他的状态也满足条件 就代表这监听者要被回调
满足条件的rlo, 这里的做法是将 满足条件的 rlo 加入到了 缓冲区里collectedObservers
*/
if (0 != (rlo->_activities & activity) && __CFIsValid(rlo) && !__CFRunLoopObserverIsFiring(rlo)) {
collectedObservers[obs_cnt++] = (CFRunLoopObserverRef)CFRetain(rlo);
}
}
//线程安全访问
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
/*
将上面搜集到的 监听者, 从缓存里遍历出来
在执行任务 Observer的回调之前,先将 observer的 firing 设置为1, 代表其他地方在同时访问的时候, 正在回调
然后调用了 很出名的 CALLING_OUT函数__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
执行完 监听的回调之后, 会将 firing 为设置为0, 表示回调不在执行中
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__的函数很简单,
取出函数指针(回调) rlo->_callout
取出参数 rlo, activity, rlo->_context.info
然后回调 rlo->_callout(rlo, activity, rlo->_contex.info)
具体这些参数怎么初始化的, 去看创建Observer
*/
for (CFIndex idx = 0; idx < obs_cnt; idx++) {
CFRunLoopObserverRef rlo = collectedObservers[idx];
__CFRunLoopObserverLock(rlo);
if (__CFIsValid(rlo)) {
Boolean doInvalidate = !__CFRunLoopObserverRepeats(rlo);
__CFRunLoopObserverSetFiring(rlo);
__CFRunLoopObserverUnlock(rlo);
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(rlo->_callout, rlo, activity, rlo->_context.info);
if (doInvalidate) {
CFRunLoopObserverInvalidate(rlo);
}
__CFRunLoopObserverUnsetFiring(rlo);
} else {
__CFRunLoopObserverUnlock(rlo);
}
CFRelease(rlo);
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
if (collectedObservers != buffer) free(collectedObservers);
}
上面的是我们经常在函数调用栈里 看到的
CALLING_OUT_XXX
,其实这个函数就是取出函数指针, 参数 然后回调, 具体看代码注释, 接下来看看创建Observer
CFRunLoopObserverCreate(公开的)
CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator,
CFOptionFlags activities,
Boolean repeats,
CFIndex order,
CFRunLoopObserverCallBack callout,
CFRunLoopObserverContext *context) {
//相关的进程检查
CHECK_FOR_FORK();
//创建CFRunLoopObserverRef
CFRunLoopObserverRef memory;
UInt32 size;
size = sizeof(struct __CFRunLoopObserver) - sizeof(CFRuntimeBase);
memory = (CFRunLoopObserverRef)_CFRuntimeCreateInstance(allocator, __kCFRunLoopObserverTypeID, size, NULL);
if (NULL == memory) {
return NULL;
}
//设置ob可用 bit位是3, __CFSetValid地方已经说了
__CFSetValid(memory);
//标记 ob 没开始执行
__CFRunLoopObserverUnsetFiring(memory);
//标记 是否重复
if (repeats) {
__CFRunLoopObserverSetRepeats(memory);
} else {
__CFRunLoopObserverUnsetRepeats(memory);
}
//初始化锁
__CFRunLoopLockInit(&memory->_lock);
//这里没有指定loop, 指定loop的时候是 CFRunLoopAddObserver的时候
memory->_runLoop = NULL;
//目前还没被引用,所以是0
memory->_rlCount = 0;
//初始化的时候 默认监听的是所有的状态
memory->_activities = activities;
//指定的优先级
memory->_order = order;
//下面是函数回调和回调参数设置
memory->_callout = callout;
if (context) {
if (context->retain) {
memory->_context.info = (void *)context->retain(context->info);
} else {
memory->_context.info = context->info;
}
memory->_context.retain = context->retain;
memory->_context.release = context->release;
memory->_context.copyDescription = context->copyDescription;
} else {
memory->_context.info = 0;
memory->_context.retain = 0;
memory->_context.release = 0;
memory->_context.copyDescription = 0;
}
return memory;
}