2026 年 9 月 29 日 00:41 UTC,全球大量 iOS 应用开始集体崩溃。这些应用互不相关,航空公司值机应用、导航应用、电商应用,崩溃时间却精确到同一分钟。开发者没有发布任何更新,App Store 也没有新版本上架,唯一的共同点:它们都集成了 Firebase iOS SDK。Firebase 官方仓库的 issue #16728 在两小时内聚集了数百条来自世界各地的报告,被标记为 P0。事故在 2 小时 11 分钟后通过服务端回滚修复,但它暴露的问题值得每个移动开发者认真对待:一段运行在别人服务器上的配置数据,可以让你的应用在用户手机上当场死亡,而你能做的只有等待。

崩溃时间线:从 00:41 到 19:52
把官方回复和 issue 里的报告对齐,这条时间线完整还原了事故的全过程。
00:41 UTC,第一批崩溃出现。GiftList 团队在 issue 里留下了精确的数据:他们在 Crashlytics 上看到的最早事件是 00:55:24 UTC,18 分钟内 56 次崩溃,涉及 5 个已发布版本(1.0.56 到 1.0.61)。这些版本跨越数月发布,唯一的变量在服务器侧。
01:00 前后,issue #16728 建立,报告开始涌入。一位开发者的数据可以说明波及速度:其应用崩溃量已达约 2 万次;另一位报告 11 万事件;GoDaddy 的 iOS 团队确认 117 名用户受影响;一家航空公司的两个独立应用(一个原生 Swift、一个 React Native,共享的唯一组件就是 FirebaseAnalytics)同时中招。日本 GMO 团队的报告补充了一个关键细节:他们用的是 2022 年的 Firebase iOS SDK 10.22.0,同样崩溃。事故不挑 SDK 版本。
02:24 UTC,Firebase 团队成员 ncooke3 确认问题已定位并开始回滚。03:16 UTC,回滚完成。05:31 UTC,官方总结发布:这次事故由 Google Analytics for Firebase(iOS 端)收到的一个格式错误的 payload 触发,修复无需任何 SDK 更新。由于客户端缓存,部分应用实例的崩溃可能持续到回滚完成后最多 4 小时。
从第一起崩溃到回滚完成,2 小时 35 分钟。期间全球使用 Firebase Analytics 的 iOS 应用处于"听天由命"状态。
崩溃机制:一个 nil key 如何杀死整个进程
崩溃栈被 issue 里的分析逐帧拆解过,整个链条值得完整走一遍,因为它展示了"服务端数据"变成"客户端致命异常"的全部路径。
崩溃签名是 Objective-C 开发者熟悉的经典异常:
Fatal Exception: NSInvalidArgumentException
*** -[__NSDictionaryM setObject:forKeyedSubscript:]: key cannot be nilNSMutableDictionary 不接受 nil 键,向它写入 nil key 会立即抛出 NSInvalidArgumentException。这在 Objective-C 里是常识级别的防御规则,问题在于:谁往字典里写了一个 nil key?
链条的起点是 Firebase Analytics 的实验配置拉取。SDK 启动后会定期请求 app-analytics-services.com 的 sdk-exp 端点,获取 A/B 实验配置。注意,这一步是 SDK 内部行为:即使你的应用从未配置过任何 A/B 测试,即使 Analytics 采集在 plist 里显式关闭,这个请求照发不误。#16729 的报告者就是在 Analytics 完全未启用的应用里遇到崩溃的。
09-29 00:41 UTC 起,这个端点返回的 protobuf 数据里出现了一条没有名字的 flag。SDK 的 APMEExperiment initWithExperimentID:flags: 解析每个实验条目时,以 flag 名字作为键写入 _flagsByName 字典。protobuf 字段缺失时,底层序列化库 NANOArithmetic 的取值函数返回 nil,nil key 进入 setObject:forKeyedSubscript:,异常抛出。
真正让事故升级为全球性灾难的是最后一步:_flagsByName 不是普通的 NSMutableDictionary,而是 GoogleUtilities 里的 GULMutableDictionary,它的写入操作通过 dispatch_async 派发到私有队列执行。这意味着两点:第一,异常抛在派发队列上,应用的任何 try/catch 都捕获不到,进程直接终止;第二,崩溃栈里只剩 _dispatch_call_block_and_release 一个帧,开发者代码的任何帧都不在栈里,事后从崩溃报告看到的现场完全发生在 Google 的代码里。

为什么更新 SDK 也没用:事故的结构性根源
Issue 区一个高赞总结把开发者最关心的问题讲清楚了:受影响版本横跨 10.22 到 12.19.2,从五年前的老版本到最新版全部中招,升级 SDK 无法自保。事故不挑 SDK 版本:10.22 到 12.19.2 全线中招,问题不在某个版本的代码,根子在这条贯穿所有版本的故障链,每一环都独立成立,叠加在一起才有了这次事故。
第一环:配置即代码。sdk-exp 响应是 Google 服务端随时可以变更的数据,但它直接进入客户端的关键执行路径。服务端一条没有名字的实验 flag,等价于全球所有集成方的一次静默发版,只是这次"发版"发的是致命错误。
第二环:解析无防御。SDK 收到响应后直接写入字典,没有对缺失字段做校验。protobuf 缺字段本来是常态,取值函数返回 nil 是设计内行为,崩溃不是——把 nil key 拦下来跳过该条目只需一个 if。事实上事故期间社区成员 rokgregoric 向 GoogleUtilities 提交的 PR(#249)正是这个思路:让 GULMutableDictionary 忽略 nil key。由于 GoogleUtilities 的版本约束是 8.1.3 ..< 9.0.0,这个补丁甚至不需要 Firebase 发新版本。
第三环:不可禁用。Analytics 可以在 plist 里关掉采集,但实验拉取依然运行。集成了 Firebase Analytics 的应用没有任何官方开关能阻止 SDK 处理这个 payload。开发者唯一的应对方式是等待。
第四环:发版周期错配。iOS 应用更新必须经过 App Store 审核,修复以天计;而服务端配置变更以分钟计。这个时间尺度差意味着:当服务端数据出错时,客户端没有任何对等的自救手段。
与 2020 年 Facebook SDK 事件互为镜像
2020 年 5 月 6 日,Facebook iOS SDK 的一次服务端变更让 Spotify、TikTok、Pinterest、Waze、Venmo 等大批知名应用全球性崩溃,开发者在数小时内束手无策,最终同样由 Facebook 在服务端回滚收场。当时的触发点是 SDK 启动路径上的配置读取,与这次 Firebase 的 sdk-exp 拉取如出一辙。
六年过去,两次事故的剧本没有变化:第三方 SDK 在初始化路径上读取远端数据,服务端数据出错,客户端集体崩溃,开发者只能围观。变化的只有规模,Firebase Analytics 的装机量远超当年的 Facebook SDK,RN、Flutter、原生应用全在受影响名单上。HN 讨论里开发者再次翻出"Apple 什么时候自己做一个"的老话题,但只要第三方分析/崩溃/推送 SDK 的集成模式不变,这类事故就会按同样的概率分布继续发生。
开发者能从这次事故里带走什么
事故已修复,Google 在 issue 里的官方总结确认:无需更新 SDK,残留崩溃由客户端缓存导致,回滚完成后最多 4 小时内自动消失。对大多数受影响应用,事情到此为止。但有几件事值得现在就做。
检查自己的崩溃报告。同一个崩溃在 Crashlytics 里会被拆成多个 issue(NSInvalidArgumentException、SIGABRT、变体各算一个),只看单一问题会低估影响面。这次有团队在 90 份详细事件里确认全部命中同一崩溃栈,这种核对值得复用。
评估自己的"单点依赖清单"。你的应用里有多少个 SDK 会在启动路径上读取远端数据?分析、崩溃上报、推送、远程配置、实验平台……每一个都是潜在的"sdk-exp"。这次事故里,React Native 和原生应用因为共享同一个 FirebaseAnalytics 而一起倒下,跨框架的共享依赖尤其值得排查。
对关键路径上的第三方数据保持防御性预期。社区在事故期间验证过一种应急手段:swizzle GULMutableDictionary 的 setObject:forKeyedSubscript: 丢弃 nil key,可以让应用在坏 payload 下存活。这是 hack,不建议常驻,但它证明了一件事:客户端多一层对第三方数据的校验,就能把"全球性事故"降级为"一条日志"。等待下一次事故时,你集成的 SDK 是否有这层校验,值得在集成时就问清楚。
最后是沟通机制的问题。事故全程,Firebase Status Dashboard 一路绿灯,唯一的官方信息渠道是一个 GitHub issue 的评论区,导致大量开发者浪费数小时排查自己的代码。事后多位开发者在 issue 里要求 Google 发布正式的事故报告(RCA),说明根因(那次 payload 变更因何产生)至今没有公开答案。对于把关键基础设施托付给云服务的团队,"服务出问题时官方渠道在哪里"这个问题的答案,最好在事故前就知道。
说明
- 事故时间线与官方结论来自 firebase/firebase-ios-sdk issue #16728 中 Firebase 团队成员 ncooke3 的两次正式回复(2026-09-29 02:24 UTC、05:31 UTC)
- 崩溃栈与机制分析来自 issue #16728、#16729、#16731、#16732、#16734 中多位开发者的一致报告
- 2020 年 Facebook SDK 事件细节来自 Wired 报道及 Hacker News 社区存档
- 本文不构成对 Firebase 或 Google 产品的评价建议,事故责任与 RCA 以官方后续披露为准