欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

深入解析RunLoop(上)

最编程 2024-08-03 22:28:31
...

本文的源码来做苹果的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 循环, 等待信号

  • mainkernel 2个线程里用的锁不一样, 原因是 这2个线程本来就是独立的.

  • mainkernel若用同一把锁, 那么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里断点相关的函数调用栈:

AppDelegate_load.png

上图是没有进入main函数之前, 系统加载类对象的函数调用栈, 并没有runloop相关的信息(函数)


断点AppDelegateapplication:didFinishLaunchingWithOptions:

AppDelegateDidFinishLoad.png

明显的看到, 与RunLoop 相关的函数已经被调用, 并且回调了比较显眼的函数__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__, 这些与runloop相关的函数后面会根据源码介绍的


断点AppdelegateapplicationWillResignActive: app进入后台的时候调用

AppdelegateWillEnterBG.png

和刚启动比较, 前面的几个函数调用都一样, 并且也可以发现, __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__之前的基本一样, 基本可以断定已经进入循环了.

断点App禁止的时候的调用栈 (点击 xcode 调速器区域的 暂停)

AppPasue0.png

AppPasue1.png

观察地址可以发现 mach_msg 调用了 mach_msg_trap, 这个mach_msg_trap就是内核阻塞函数, 不占用CPU资源, mach_msg在源代码里可以找到的, 下面继续看调用栈的函数:

__CFRunLoopServiceMachPort0.png
__CFRunLoopServiceMachPort1.png

虽然调用的地址在 并不是 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主动创建kCFRunLoopDefaultModemode, 第三个参数代表找不到要创建,这里的确是要创建


__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;
}

上面是 在通过ModeNamerunLoop对象去匹配 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;
    }


    ///这