# React Native iOS 排查记录:从 Yoga 编译报错,到模拟器反复白屏

15 阅读10分钟

React Native iOS 排查记录:从 Yoga 编译报错,到模拟器反复白屏

排查时间:2026 年 10 月 12—13 日。本文记录一次旧版 React Native 项目在较新 Xcode 环境中的实际排查过程。最终恢复了模拟器运行,并保存了绕过调试器暂停问题的启动方案;Xcode/LLDB 的底层原因仍未完全确认。

这次遇到的问题很容易让人误以为是同一个故障:先是编译时出现 Yoga 报错,接着应用启动后提示定位模块没有链接,最后又变成没有红屏提示的白屏。期间还把最低支持系统从 iOS 11 改成了 iOS 13,更增加了判断难度。

最后发现,需要分别处理三个问题:Yoga 的旧语法触发了新编译器的检查、应用连接了另一个项目的 Metro 服务,以及原生调试器在 Rosetta 模拟器里反复暂停应用。

1. 排查环境

项目本次环境
React Native0.68.5
Xcode26.4
模拟器系统iOS 26.4
运行目标iPhone Pro Max(Rosetta)
CocoaPods1.11.3,由项目 Bundler 管理
应用最低支持版本从 iOS 11 调整为 iOS 13

这些结论只针对本次环境。如果其他项目出现相似白屏,仍需要结合实际日志和进程状态判断。

2. 第一个问题:Yoga 的空格为什么会导致编译失败?

最开始,Xcode 在下面的文件里报错:

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++ 写法:

inline YGValue operator"" _pt(long double value) {
  return YGValue{static_cast<float>(value), YGUnitPoint};
}

这里定义的是自定义字面量运算符,用来表达点数、百分比等布局数值。新版 Clang 会对 operator"" 与后缀之间带空格的写法发出弃用警告,对应检查项是 -Wdeprecated-literal-operator。Clang 官方诊断说明

为什么警告变成了红色错误?因为项目中 Yoga 的编译参数包含:

-Werror

它会把警告当作错误处理。因此,虽然布局逻辑没有变化,这种旧语法仍然可能阻断编译。

修复方式

去掉 "" 和后缀之间的空格:

// 修改前
operator"" _pt
operator"" _percent

// 修改后
operator""_pt
operator""_percent

本项目的这个头文件共有 6 处需要调整,包含运算符定义和内部调用。

直接修改 node_modules 只能修好当前安装目录。为了避免重装依赖后丢失,新增了 scripts/fix-yoga-literals.js,并在 package.json 中配置:

{
  "scripts": {
    "postinstall": "node scripts/fix-yoga-literals.js"
  }
}

脚本的核心处理是:

const patched = source.replace(
  /operator""[ \t]+(_pt|_percent)\b/g,
  'operator""$1',
);

脚本只在内容发生变化时写回文件,重复执行不会继续修改已修好的内容。如果安装依赖时禁用了生命周期脚本,可以手动执行:

node scripts/fix-yoga-literals.js

修复后,使用本机 Clang 对该头文件及整数、小数形式的点数和百分比字面量做了编译检查,开启 -Wall -Werror 后检查通过。这个验证说明该处语法问题已经消除,完整应用是否能启动还需要继续验证。

3. 第二个问题:提示定位模块没链接,实际却连错了项目

应用运行后出现红屏:

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 端口。先查监听进程:

lsof -nP -iTCP:8081 -sTCP:LISTEN

记下输出中的 PID,再查看该进程的工作目录:

# 将 12345 替换为上一条命令查到的 PID
lsof -a -p 12345 -d cwd -Fn

这次查到的目录属于另一个 React Native 项目。那个项目恰好安装了红屏提到的定位包,其依赖代码中也包含相同报错和调用栈里的函数名。

这些证据指向:当前 iOS 应用连接了另一个项目的 Metro 服务,加载的 JS 与应用内编译好的原生模块不匹配。

处理方式

关闭另一个项目的 Metro 服务,在当前项目根目录启动:

npm start -- --reset-cache

然后重新加载应用,并再次确认 8081 对应的工作目录。

本次排查的关键是进程所属项目。重置缓存可以帮助清理旧缓存,但无法替代“确认服务属于正确项目”这一步。

4. 第三个问题:白屏时,应用实际上被调试器暂停了

后续应用不再显示定位模块红屏,却出现了白屏。Xcode 顶部显示:

Paused scm on iPhone Pro Max

线程停在:

dyld`lldb_image_notifier
Thread 2: trace

其中一次暂停发生在 AXBundleLoadQueue,另一次发生在主线程。通过 LLDB 查询,得到的停止原因是:

stop reason = trace

为什么暂停会表现成白屏?

LLDB 是 Xcode 使用的原生调试器。它附加到应用后,可以暂停进程,让开发者查看变量、调用栈和断点。

一旦进程被暂停,页面渲染和事件处理也会停下来。如果此时页面尚未完成绘制,模拟器就可能一直停留在白色背景。

最初点击“继续执行”后,应用能够恢复。但后来重新启动又出现相同暂停,说明仅恢复一次运行还不够,需要进一步隔离调试器的影响。

日志提供了哪些证据?

新日志中可以看到:

Running application scm
componentDidMount-app.js
HomeHomeHome

同时还有接口返回和首页相关输出。这说明 JS 已经执行到了应用挂载、首页渲染等路径。虽然这些日志不能单独证明页面一定显示成功,但与 Xcode 的暂停状态结合后,调试器成为了明确的排查方向。

日志里还出现了 dSYM、线程性能检查和模拟器系统服务等提示。它们值得分别理解,但这次没有证据表明其中某一条就是反复白屏的直接原因。

用实际启动验证调试器的影响

本次进行了三步验证:

  1. 在暂停状态下分离 LLDB,应用随后恢复首页。
  2. 不附加 LLDB,终止并重新启动应用,首页正常显示。
  3. 创建不附加 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使用场景
scmDebug是需要 iOS 原生断点调试时使用
scm-simulatorDebug否当前环境下,在模拟器里运行页面、开发 JS

Debug 构建与附加 LLDB 是两个不同设置。 关闭原生调试器后,应用仍使用 Debug 构建,JS 热更新和日志仍然可用,但该方案无法直接使用 LLDB 原生断点。

在 Xcode 中创建

本次使用的是复制现有方案的方法:

  1. 选择 Product → Scheme → Edit Scheme…。
  2. 点击 Duplicate Scheme,复制当前方案。
  3. 将副本命名为 scm-simulator。
  4. 在新方案中选择 Run → Info。
  5. 保持 Build Configuration = Debug。
  6. 取消勾选 Debug executable。
  7. 勾选 Shared,将方案保存为项目内可共享的配置。
  8. 关闭编辑窗口。

生成的文件是:

ios/scm.xcodeproj/xcshareddata/xcschemes/scm-simulator.xcscheme

其中启动配置的关键内容为:

<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 当时仍然是:

platform :ios, '11.0'

最低支持版本表示“应用最早能运行在哪个 iOS 版本”,并不表示把编译 SDK 或模拟器系统切换成该版本。Apple 构建设置说明

决定最低支持 iOS 13 后,同步修改了 Podfile:

platform :ios, '13.0'

随后重新安装 Pods,成功更新了集成配置。Podfile.lock 中依赖版本没有变化,只更新了 Podfile 校验值。CocoaPods 的 platform 配置说明

但同步后,LLDB 暂停仍然出现。因此,目前没有证据说明白屏是从 iOS 11 改成 13 导致的,执行 pod install 也没有解决调试器暂停。

另外,应用最低版本高于某个依赖的最低版本,通常是允许的。同步 Podfile 不代表必须把每一个第三方 Pod 的最低版本都强制设成 13。

为什么直接执行 pod install 找不到命令?

当前项目通过 Bundler 管理 CocoaPods,直接执行时出现:

zsh: command not found: pod

项目中实际使用并安装成功的命令是:

# 从项目根目录进入 ios
cd ios
bundle exec pod install

bundle exec 会使用项目 Gemfile 约束的工具环境。如果换到新电脑,还需要先准备项目要求的 Ruby 和 Bundler 依赖。

7. 这次排查留下的经验

遇到相似问题,可以按下面的顺序缩小范围:

  1. 先判断阶段。 编译失败、启动红屏和运行白屏需要分别找证据。
  2. 核对报错中的依赖是否属于当前项目。 如果包名与项目依赖对不上,检查 Metro 进程的工作目录。
  3. 白屏时先看 Xcode 是 Running 还是 Paused。 暂停状态下,页面无法继续更新。
  4. 把日志与实际界面结合。 出现首页日志不等于首页已经显示,仍要检查模拟器。
  5. 用对照实验验证推断。 本次比较了附加 LLDB、分离 LLDB和不附加 LLDB重新启动的行为。
  6. 区分修复与绕过。 Yoga 语法已修复;Metro 连接需确认项目归属;新的 Scheme 绕过了调试器问题,底层兼容原因仍待进一步确认。

最终,本次保留下来的项目改动包括 Yoga 安装后修复脚本、iOS 13 的 Podfile 配置,以及 scm-simulator 共享运行方案。最后一项已经通过 Xcode 构建启动和模拟器首页显示验证。