遇到 -[__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 下次刷新窗口之前仍会持续崩溃。
即:修复是服务端回滚配置,无需客户端发版。
临时规避方案
- 重启 App: 引导用户彻底退出并重新启动,等待 SDK 拉取新配置。
- 监控崩溃率: 通过 Crashlytics、Sentry 等持续观测,确认恢复时间点。
- 评估降级: 若业务允许,临时移除 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 / 官方状态页。
- 为关键业务路径做好兜底和降级,降低对单一依赖的脆弱性。
希望这篇复盘对你有帮助。如果你也受到了这次事故影响,欢迎评论区交流。