Firebase iOS SDK 重大事故:一个 nil key 如何让全球 App 集体崩溃

遇到 -[__NSDictionaryM setObject:forKeyedSubscript:]: key cannot be nil 崩溃?这篇文章告诉你根因

2026-09-29 00:41 UTC,Firebase 服务端悄悄推送了一次配置变更。

没有任何 App 发版,崩溃率却在 18 分钟内从 0 飙升至数十万次。


事故概述

2026 年 9 月 29 日凌晨,全球大量使用 Firebase Analytics SDK 的 iOS 应用同时出现崩溃,崩溃率在数分钟内急剧攀升。受影响的开发者陆续在 GitHub 上聚集,最终汇聚成 Firebase iOS SDK 仓库的 Issue #16728。

  • 受影响版本: Firebase iOS SDK 12.14.0
  • 安装方式: Swift Package Manager、React Native Firebase 等均受影响
  • 触发条件: 无需 App 更新,启动即可能崩溃

崩溃现场

所有开发者上报的崩溃堆栈高度一致:

text 复制代码
Fatal Exception: NSInvalidArgumentException
*** -[__NSDictionaryM setObject:forKeyedSubscript:]: key cannot be nil

完整调用栈:

text 复制代码
4  GoogleUtilities  -[GULMutableDictionary dictionary] (GULMutableDictionary.m:98)
5  Runner           -[APMEExperiment copyWithZone:]
6  Runner           -[APMESnapshot initWithSDKName:experiments:]
7  Runner           -[APMESnapshot initWithProtobuf:]
8  Runner           -[APMETaskManager experimentSnapshotsFromExperimentResponse:]
9  Runner           -[APMETaskManager handleFetchingExperimentsResponse:data:error:]
10 Runner           __35-[APMETaskManager fetchExperiments]_block_invoke

崩溃发生在 APMExperimentWorkerQueue 处理 POST /sdk-exp 响应(HTTP 200)后不到 1 秒。由于 GULMutableDictionary 通过 dispatch_async 写入,调用方栈帧已被释放,因此最终调用栈中可能只剩下 _dispatch_call_block_and_release。


根因分析

整条链路逻辑清晰:

text 复制代码
服务端 /sdk-exp 接口
  └─ 返回实验配置 Protobuf
      └─ APMETaskManager 解析响应
          └─ APMESnapshot initWithProtobuf:
              └─ APMEExperiment copyWithZone:
                  └─ GULMutableDictionary setObject:forKeyedSubscript:
                      └─ 💥 key == nil → NSInvalidArgumentException

根本原因: 服务端在 00:41 UTC 推送的实验配置中,某个实验条目的 key 字段为空(nil)。SDK 客户端将其写入 GULMutableDictionary 时未做 nil 校验,直接触发系统异常崩溃。

这是一个典型的服务端配置驱动的客户端崩溃:

  • 服务端: 缺乏配置数据的合法性校验。
  • 客户端: 缺乏对不可信数据的防御性处理。

影响规模

维度 数据
事故开始时间 2026-09-29 00:41 UTC
首批崩溃(18 分钟内) 56 次崩溃 / 56 个用户
部分用户峰值 110,000+ crash 事件
用户崩溃率 最高约 27%
GitHub 跟帖数 450+ 条评论
受影响平台 iOS 原生、React Native Firebase
是否需要 App 更新触发 否,已发布版本自动受影响

受影响的不乏金融类 App、导航类 App,以及多个 10 万+ DAU 产品。


官方响应与修复

Firebase 官方成员 ncooke3 于事故发生后回复:

修复的配置下发已完成。但由于存在缓存行为,部分 App 实例可能在 SDK 下次刷新窗口之前仍会持续崩溃。

即:修复是服务端回滚配置,无需客户端发版。


临时规避方案

  1. 重启 App: 引导用户彻底退出并重新启动,等待 SDK 拉取新配置。
  2. 监控崩溃率: 通过 Crashlytics、Sentry 等持续观测,确认恢复时间点。
  3. 评估降级: 若业务允许,临时移除 Firebase Analytics 依赖并紧急发版。

深层反思

客户端:防御性编程缺失

objc 复制代码
// 事故代码的等价逻辑(伪代码)
[dict setObject:value forKey:key]; // key 未做 nil 检查 💥

// 正确做法
if (key && value) {
    [dict setObject:value forKey:key];
}

对于来自网络的任何数据,都应视为不可信输入。在写入集合类型前校验 nil 是 Objective-C / Swift 开发的基本守则,SDK 级别的代码尤其不应省略。

服务端:配置变更缺乏灰度与校验

  • 配置数据应有 Schema 校验,拒绝 key 为空的条目进入下发链路。
  • 影响全量客户端的配置变更,应走灰度发布,而非全量推送。
  • 应设置自动回滚机制:崩溃率超阈值时自动触发配置回退。

写在最后

一个没有被保护的 nil,可以在 18 分钟内让全球数十万用户的 App 崩溃。

对于依赖第三方 SDK 的开发者,我们能做的是:

  • 建立完善的崩溃监控与报警,第一时间感知异常。
  • 订阅所用 SDK 的 GitHub Issue / 官方状态页。
  • 为关键业务路径做好兜底和降级,降低对单一依赖的脆弱性。

希望这篇复盘对你有帮助。如果你也受到了这次事故影响,欢迎评论区交流。


参考

相关推荐
鹤卿1234 小时前
「iOS」GCD
开发语言·ios·objective-c
黑科技iOS上架5 小时前
小蟹iOS编译级混淆与定向加固配合使用完整教程
ios·审核·ios混淆·执行程序差异化
传奇开心果编程5 小时前
【Flutter入门练中学】第3课:滚动与列表
android·学习·flutter·ui·ios
for_ever_love__5 小时前
iOS: CABasicAnimation动画
macos·ios·cocoa
天神哥哥啊6 小时前
unity联调注意事项-IOS
unity·ios·游戏引擎
Martin -Tang6 小时前
uniapp app嵌套webview 弹窗方式
android·ios·uni-app
可乐鸡翅yeah_20 小时前
hls.js 切换多个视频源,新手开发常见踩坑
开发语言·前端·javascript·ios·ffmpeg·音视频·safari
传奇开心果编程20 小时前
【Flutter入门练中学】第8课:动画与过渡
android·学习·flutter·ui·ios
传奇开心果编程1 天前
【Flutter入门练中学】第11课:状态管理进阶与声明式路由
android·学习·flutter·ui·ios