React Native iOS 排查记录:从 Yoga 编译报错,到模拟器反复白屏
排查时间:2026 年 10 月 12---13 日。本文记录一次旧版 React Native 项目在较新 Xcode 环境中的实际排查过程。最终恢复了模拟器运行,并保存了绕过调试器暂停问题的启动方案;Xcode/LLDB 的底层原因仍未完全确认。
这次遇到的问题很容易让人误以为是同一个故障:先是编译时出现 Yoga 报错,接着应用启动后提示定位模块没有链接,最后又变成没有红屏提示的白屏。期间还把最低支持系统从 iOS 11 改成了 iOS 13,更增加了判断难度。
最后发现,需要分别处理三个问题:Yoga 的旧语法触发了新编译器的检查、应用连接了另一个项目的 Metro 服务,以及原生调试器在 Rosetta 模拟器里反复暂停应用。
1. 排查环境
| 项目 | 本次环境 |
|---|---|
| React Native | 0.68.5 |
| Xcode | 26.4 |
| 模拟器系统 | iOS 26.4 |
| 运行目标 | iPhone Pro Max(Rosetta) |
| CocoaPods | 1.11.3,由项目 Bundler 管理 |
| 应用最低支持版本 | 从 iOS 11 调整为 iOS 13 |
这些结论只针对本次环境。如果其他项目出现相似白屏,仍需要结合实际日志和进程状态判断。
2. 第一个问题:Yoga 的空格为什么会导致编译失败?
最开始,Xcode 在下面的文件里报错:
text
node_modules/react-native/ReactCommon/yoga/yoga/YGValue.h:77:27
Identifier '_pt' preceded by whitespace in a literal operator declaration is deprecated
检查源码后,发现 Yoga 中存在这样的 C++ 写法:
cpp
inline YGValue operator"" _pt(long double value) {
return YGValue{static_cast<float>(value), YGUnitPoint};
}
这里定义的是自定义字面量运算符,用来表达点数、百分比等布局数值。新版 Clang 会对 operator"" 与后缀之间带空格的写法发出弃用警告,对应检查项是 -Wdeprecated-literal-operator。Clang 官方诊断说明
为什么警告变成了红色错误?因为项目中 Yoga 的编译参数包含:
text
-Werror
它会把警告当作错误处理。因此,虽然布局逻辑没有变化,这种旧语法仍然可能阻断编译。
修复方式
去掉 "" 和后缀之间的空格:
cpp
// 修改前
operator"" _pt
operator"" _percent
// 修改后
operator""_pt
operator""_percent
本项目的这个头文件共有 6 处需要调整,包含运算符定义和内部调用。
直接修改 node_modules 只能修好当前安装目录。为了避免重装依赖后丢失,新增了 scripts/fix-yoga-literals.js,并在 package.json 中配置:
json
{
"scripts": {
"postinstall": "node scripts/fix-yoga-literals.js"
}
}
脚本的核心处理是:
js
const patched = source.replace(
/operator""[ \t]+(_pt|_percent)\b/g,
'operator""$1',
);
脚本只在内容发生变化时写回文件,重复执行不会继续修改已修好的内容。如果安装依赖时禁用了生命周期脚本,可以手动执行:
bash
node scripts/fix-yoga-literals.js
修复后,使用本机 Clang 对该头文件及整数、小数形式的点数和百分比字面量做了编译检查,开启 -Wall -Werror 后检查通过。这个验证说明该处语法问题已经消除,完整应用是否能启动还需要继续验证。
3. 第二个问题:提示定位模块没链接,实际却连错了项目
应用运行后出现红屏:
text
The package '@react-native-community/geolocation' doesn't seem to be linked.
报错建议执行 pod install 并重新构建。但检查项目依赖后发现了矛盾:
- 当前项目使用的是
react-native-amap-geolocation。 - 当前项目没有安装
@react-native-community/geolocation。 - 高德定位已经存在于
Podfile.lock和 Pods 工程中。
因此,下一步应该确认"当前应用到底加载了哪个项目的 JS",而不是立即安装红屏里提到的包。
检查 8081 端口属于哪个项目
Metro 是 React Native 开发时提供 JS 代码的本地服务,通常监听 8081 端口。先查监听进程:
bash
lsof -nP -iTCP:8081 -sTCP:LISTEN
记下输出中的 PID,再查看该进程的工作目录:
bash
# 将 12345 替换为上一条命令查到的 PID
lsof -a -p 12345 -d cwd -Fn
这次查到的目录属于另一个 React Native 项目。那个项目恰好安装了红屏提到的定位包,其依赖代码中也包含相同报错和调用栈里的函数名。
这些证据指向:当前 iOS 应用连接了另一个项目的 Metro 服务,加载的 JS 与应用内编译好的原生模块不匹配。
处理方式
关闭另一个项目的 Metro 服务,在当前项目根目录启动:
bash
npm start -- --reset-cache
然后重新加载应用,并再次确认 8081 对应的工作目录。
本次排查的关键是进程所属项目。重置缓存可以帮助清理旧缓存,但无法替代"确认服务属于正确项目"这一步。
4. 第三个问题:白屏时,应用实际上被调试器暂停了
后续应用不再显示定位模块红屏,却出现了白屏。Xcode 顶部显示:
text
Paused scm on iPhone Pro Max
线程停在:
text
dyld`lldb_image_notifier
Thread 2: trace
其中一次暂停发生在 AXBundleLoadQueue,另一次发生在主线程。通过 LLDB 查询,得到的停止原因是:
text
stop reason = trace
为什么暂停会表现成白屏?
LLDB 是 Xcode 使用的原生调试器。它附加到应用后,可以暂停进程,让开发者查看变量、调用栈和断点。
一旦进程被暂停,页面渲染和事件处理也会停下来。如果此时页面尚未完成绘制,模拟器就可能一直停留在白色背景。
最初点击"继续执行"后,应用能够恢复。但后来重新启动又出现相同暂停,说明仅恢复一次运行还不够,需要进一步隔离调试器的影响。
日志提供了哪些证据?
新日志中可以看到:
text
Running application scm
componentDidMount-app.js
HomeHomeHome
同时还有接口返回和首页相关输出。这说明 JS 已经执行到了应用挂载、首页渲染等路径。虽然这些日志不能单独证明页面一定显示成功,但与 Xcode 的暂停状态结合后,调试器成为了明确的排查方向。
日志里还出现了 dSYM、线程性能检查和模拟器系统服务等提示。它们值得分别理解,但这次没有证据表明其中某一条就是反复白屏的直接原因。
用实际启动验证调试器的影响
本次进行了三步验证:
- 在暂停状态下分离 LLDB,应用随后恢复首页。
- 不附加 LLDB,终止并重新启动应用,首页正常显示。
- 创建不附加 LLDB 的 Xcode 运行方案,用该方案重新构建并启动,首页再次正常显示。
这组结果支持:本次白屏与 LLDB 暂停有关;不附加调试器可以绕过目前的问题。
Apple 开发者论坛也有 Xcode 26 停在 lldb_image_notifier 的类似报告,其中有开发者反馈问题与 Apple Silicon 上的 Rosetta 模拟器有关。相关讨论
结合本项目使用的 Rosetta 运行目标,证据指向 Xcode/LLDB 与 Rosetta 模拟器之间的调试兼容问题。不过,论坛反馈和本次验证都不足以确认具体底层缺陷,也不能据此认定所有同类白屏都是相同原因。
5. 最终处理:保存一套不附加 LLDB 的运行方案
为了避免每次启动都手动处理暂停,保留原来的 scm 方案,并新增了 scm-simulator。
Scheme 是什么?
Scheme 可以理解成"编译和启动应用的一套设置"。它决定使用哪个 Target、哪种构建配置,以及启动时是否附加调试器。
项目创建时,Xcode 通常已经生成默认 Scheme。只有需要保存不同启动方式时,才有必要再复制一份。
本次两个方案的主要区别如下:
| 运行方案 | 构建配置 | 启动时附加 LLDB | 使用场景 |
|---|---|---|---|
scm |
Debug | 是 | 需要 iOS 原生断点调试时使用 |
scm-simulator |
Debug | 否 | 当前环境下,在模拟器里运行页面、开发 JS |
Debug 构建与附加 LLDB 是两个不同设置。 关闭原生调试器后,应用仍使用 Debug 构建,JS 热更新和日志仍然可用,但该方案无法直接使用 LLDB 原生断点。
在 Xcode 中创建
本次使用的是复制现有方案的方法:
- 选择 Product → Scheme → Edit Scheme...。
- 点击 Duplicate Scheme,复制当前方案。
- 将副本命名为
scm-simulator。 - 在新方案中选择 Run → Info。
- 保持 Build Configuration = Debug。
- 取消勾选 Debug executable。
- 勾选 Shared,将方案保存为项目内可共享的配置。
- 关闭编辑窗口。
生成的文件是:
text
ios/scm.xcodeproj/xcshareddata/xcschemes/scm-simulator.xcscheme
其中启动配置的关键内容为:
xml
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
...>
以后怎样切换?
点击 Xcode 顶部、设备名称左边的方案名称,在列表中选择:
- 日常在模拟器里运行:
scm-simulator。 - 需要 iOS 原生断点:
scm。
然后选择运行设备,点击 ▶。
scm-simulator 是自定义名字,名字本身不会限制只能选择模拟器。它保存的是"不附加 LLDB"的启动设置,使用时仍需确认右侧运行目标。
这个处理属于绕过办法。切回原方案、继续在相同 Rosetta 环境附加 LLDB 时,仍可能再次遇到暂停。要彻底解决,后续还需要验证更新的调试环境,或在项目依赖支持的情况下评估普通 arm64 模拟器。
6. iOS 11 改成 13,与这次白屏有什么关系?
排查期间,应用 Target 的最低系统版本被从 11.0 调整为 13.0,但 Podfile 当时仍然是:
ruby
platform :ios, '11.0'
最低支持版本表示"应用最早能运行在哪个 iOS 版本",并不表示把编译 SDK 或模拟器系统切换成该版本。Apple 构建设置说明
决定最低支持 iOS 13 后,同步修改了 Podfile:
ruby
platform :ios, '13.0'
随后重新安装 Pods,成功更新了集成配置。Podfile.lock 中依赖版本没有变化,只更新了 Podfile 校验值。CocoaPods 的 platform 配置说明
但同步后,LLDB 暂停仍然出现。因此,目前没有证据说明白屏是从 iOS 11 改成 13 导致的,执行 pod install 也没有解决调试器暂停。
另外,应用最低版本高于某个依赖的最低版本,通常是允许的。同步 Podfile 不代表必须把每一个第三方 Pod 的最低版本都强制设成 13。
为什么直接执行 pod install 找不到命令?
当前项目通过 Bundler 管理 CocoaPods,直接执行时出现:
text
zsh: command not found: pod
项目中实际使用并安装成功的命令是:
bash
# 从项目根目录进入 ios
cd ios
bundle exec pod install
bundle exec 会使用项目 Gemfile 约束的工具环境。如果换到新电脑,还需要先准备项目要求的 Ruby 和 Bundler 依赖。
7. 这次排查留下的经验
遇到相似问题,可以按下面的顺序缩小范围:
- 先判断阶段。 编译失败、启动红屏和运行白屏需要分别找证据。
- 核对报错中的依赖是否属于当前项目。 如果包名与项目依赖对不上,检查 Metro 进程的工作目录。
- 白屏时先看 Xcode 是 Running 还是 Paused。 暂停状态下,页面无法继续更新。
- 把日志与实际界面结合。 出现首页日志不等于首页已经显示,仍要检查模拟器。
- 用对照实验验证推断。 本次比较了附加 LLDB、分离 LLDB和不附加 LLDB重新启动的行为。
- 区分修复与绕过。 Yoga 语法已修复;Metro 连接需确认项目归属;新的 Scheme 绕过了调试器问题,底层兼容原因仍待进一步确认。
最终,本次保留下来的项目改动包括 Yoga 安装后修复脚本、iOS 13 的 Podfile 配置,以及 scm-simulator 共享运行方案。最后一项已经通过 Xcode 构建启动和模拟器首页显示验证。