iOS RunLoop(二)生命周期与运行逻辑

status
category
date
summary
slug
icon
tags
password
上一篇主要介绍了 RunLoop 的相关概念,这一篇将通过源码学习 RunLoop 从创建到运行的具体过程,了解上一篇概念的具体运用,以及在循环内 RunLoop 做了哪些工作。题外话:这一篇开始阅读 CFRunLoop.h 和 CFRunLoop.c 的流程代码,不知道是不是我编辑器的编码和 Tab 格式没有设置好,这份苹果开源的代码缩进非常混乱。
  • iOS RunLoop(一)概念与构成
  • iOS RunLoop(二)生命周期与运行逻辑
  • iOS RunLoop(三)应用与实践
版本:CF-1153.18
为了学习 RunLoop 的基础知识,本系列在截取源码时省略了很多部分,例如结构体定义、锁的操作、指针的释放等等,省略的代码也非常重要,但是比较影响阅读速度,暂时忽略也并不影响学习整体概念。

RunLoop 的获取

只有主线程会自动创建并运行 RunLoop,其他线程不能直接创建,而是在运行时获取,如果没有则会自动创建:
CFRunLoop.c 中获取 RunLoop 的函数大致如下(省略并修改了部分代码):
RunLoop 的创建和获取过程已经在注释中标明了,总结起来就是有一个全局字典保存了线程和 RunLoop 的对应关系。字典初始化时,会为主线程创建 RunLoop 并将其加入字典。在线程中获取 RunLoop 时,会以线程为 key 在字典中查找,有则取出,无则创建,同时会为 RunLoop 注册线程销毁时的回调,保证 RunLoop 资源的合理释放。

RunLoop 的启动

启动 RunLoop 有两个函数:
值得注意的是,只有第二个函数指定了 RunLoop 的超时时间,这是平常使用时的一个小坑,会在后面的文章中提到。可以看到这两个函数最终都是通过一个函数来运行 RunLoop:
可以看到,RunLoop 在启动时如果 RunLoopMode 为空或者没有 modeItem(timer/source/observer),RunLoop 将直接退出。RunLoop 不能直接切换 RunLoopMode,只能先退出,然后指定一个 RunLoopMode 重新启动。因此在启动之前,RunLoop 会保存上一个 RunLoopMode,以便在 RunLoop 结束或者需要切换 RunLoopMode 时,可以重新恢复到之前的状态。

RunLoop 的一次正式运行

启动之后正式的运行,实际上是调用了以下函数:
这个函数比较长,不便于一次直接讲解,我就按照逻辑顺序分块描述,并且去掉了一些平台相关的宏分支判断。

检查停止状态

__CFRunLoopIsStopped 函数判断的是 RunLoop 当前一次循环是否停止,如果停止则重新设置 perRunData。根据名字我们可以猜测到 perRunData 的作用,前面提到 RunLoop 是一个循环,那么每一次循环的相关数据就保存在 perRunData 中:
不管是整个 RunLoop 的当前一次循环停止了,还是即将进入的 RunLoopMode 标记了停止,本次运行都将停止,并返回 kCFRunLoopRunStopped。

设置通信端口

其中一些变量修改的代码被隐藏了,只是在头文件中被宏定义:

RunLoop 中的循环

RunLoop 最核心的地方就是这里的 do-while 循环,在这个循环中处理 timer/source/observer,每次循环处理完这些事件就会检查当前的 RunLoop 状态,看是否超时、是否需要返回等等。可以说整个 iOS 事件驱动系统的基石就是这个循环。

总结

本文介绍了 RunLoop 的运行逻辑,用一张图来概括它的生命周期:
notion image
Loading...

© 刘口子 2018-2025