如果你用iOS 27 SDK编译App却没有适配UIScene lifecycle,App根本无法启动。而一个无法启动的App永远不会去注册推送,所以你的通知会悄无声息地停止,崩溃日志里也看不到任何指向推送的线索。表面上看像是投递问题,实际上是启动问题。

这个坑最容易让出海团队措手不及,因为推送和App启动在代码里、在脑子里都是完全不相关的两块。iOS 27把它们绑在了一起。本文讲清楚具体会坏在哪里、静默失效的完整链条、谁会受影响,以及迁移清单。Pushwoosh是一个面向出海App的客户互动平台,我们的iOS SDK已经处理好了这次迁移,但下面这套修复方案不管你用不用我们的SDK都适用。

📖

本文是iOS 27版本适配指南系列的一部分。SDK改造进行中,也顺便核实一下你的真实设备覆盖率

到底坏在哪里

Apple在WWDC25上宣布,iOS 26之后的下一个版本会强制要求所有用最新SDK编译的UIKit App适配UIScene lifecycle。iOS 27就是这个版本,这项要求现在已经生效。

触发条件很关键,大多数恐慌帖都搞错了这一点:强制生效的条件是你在Xcode 27上用iOS 27 SDK重新编译App,而不是用户在iOS 27设备上打开你现有的旧版本App。你线上已发布的版本在升级后的手机上照样能跑。真正出问题是在你下一次用新SDK编译提交的时候。这个区别给了你一点缓冲时间,但仅限于下一次提交之前。

有一点没变,恐慌类文章往往在这一点上说过头了:APNs流程本身完全没动。你依然要请求授权,依然调用registerForRemoteNotifications(),依然在didRegisterForRemoteNotificationsWithDeviceToken拿到device token。这个回调仍然留在AppDelegate里——目前iOS 27并没有对应的scene版本可以迁过去。推送机制本身没问题,Apple动的是启动流程。

让推送静默失效的完整链条

一个没有scene manifest的App用iOS 27 SDK编译后,故障会按这个顺序发生:

  1. UIKit在启动时要求scene适配,但在Info.plist里找不到UIApplicationSceneManifest
  2. App在任何AppDelegate方法执行之前就终止了。
  3. 因为application(_:didFinishLaunchingWithOptions:)根本没执行,registerForRemoteNotifications()也不会触发。
  4. 没有注册,didRegisterForRemoteNotificationsWithDeviceToken永远不会被调用,App也就永远拿不到APNs token。
  5. 没有token就没有投递。而且因为App在启动阶段就崩了,日志指向的是启动流程,而不是推送管道。

这个问题之所以特别耗排查时间,是因为症状和根因离得太远。你看到的是投递数据掉了,第一反应是去查推送服务商、payload、证书。但真正的故障点在三层之外的一个manifest文件里,跟通知本身毫无关系。

谁会受影响

这项要求针对的是UIKit App,但具体影响程度取决于你的技术栈。

技术栈受影响程度
原生UIKit直接受影响。谁掌控AppDelegate生命周期,谁就要负责这次迁移。
SwiftUI如果用的是已经基于scene的App protocol和WindowGroup,风险较低。仍依赖UIApplicationDelegateAdaptor并自定义窗口逻辑的App需要重点检查。
Flutter近期版本会为未修改AppDelegate的App自动迁移,但任何自定义原生逻辑都要手动迁移。
React Native取决于你的原生模块,以及任何挂钩AppDelegate的第三方SDK封装层。
技术栈
1 / 4
原生UIKit
受影响程度
直接受影响。谁掌控AppDelegate生命周期,谁就要负责这次迁移。
技术栈
2 / 4
SwiftUI
受影响程度
如果用的是已经基于scene的App protocol和WindowGroup,风险较低。仍依赖UIApplicationDelegateAdaptor并自定义窗口逻辑的App需要重点检查。
技术栈
3 / 4
Flutter
受影响程度
近期版本会为未修改AppDelegate的App自动迁移,但任何自定义原生逻辑都要手动迁移。
技术栈
4 / 4
React Native
受影响程度
取决于你的原生模块,以及任何挂钩AppDelegate的第三方SDK封装层。

对跨平台团队来说,真正的难点不是框架本身,而是叠加在框架之上的SDK。任何通过包裹你AppDelegate来完成自身安装的推送或分析SDK,都可能在该厂商发布scene支持之前一直卡住你的迁移。如果@main入口点被某个依赖占用,你等的就不只是自己团队的进度,还有对方的发版节奏。我们见过一次迁移因为唯一一个不配合的依赖卡了整整2周,而团队自己能控制的部分早就做完了。在假设迁移能一天搞定之前,先确认你的推送SDK对scene的兼容情况。如果你用的是我们的Flutter插件,已知迁移问题这个页面是第一站。

迁移清单

这套修复方案自2019年scene首次引入以来就没变过,变的只是它从可选变成了强制。

  • 添加scene manifest。 在Info.plist里加入UIApplicationSceneManifest,或者通过代码用UIApplicationDelegate和UISceneDelegate的方法配置scene。
  • 创建SceneDelegate。 如果没有就添加一个,并确认它真的被编译进了你的target。项目里存在SceneDelegate文件但从未加入build sources,是一个常见的坑。
  • 迁移窗口创建逻辑。 窗口的归属从AppDelegate移出。用UIWindow(windowScene:)替换UIWindow(frame:),从scene里承载你的根视图。
  • 迁移UI生命周期处理。 前台、后台、active/inactive切换都迁移到UISceneDelegate。迁移完成后,UIKit不再调用AppDelegate上的UI状态方法,留在那里的代码会悄悄失效。
  • 推送注册逻辑保持原位。 token注册和相关回调仍然留在AppDelegate里,不要为了对称性把它们也搬过去。
  • 迁移URL和deep link处理。 传入的URL现在通过scene方法到达。如果你的推送会打开特定页面,这一步是保证deep link继续可用的关键。
  • 重新编译并验证token签发。 用iOS 27 SDK编译、启动App,确认能拿到APNs token。如果拿不到,可以从iOS错误排查指南开始查。上线前用真实构建版本验证,而不是上线后再补验证。

保留AppDelegate。团队最容易看到”AppDelegate要消失了”这种说法就理解错了——它不会消失,只是角色收窄到进程级和App级事件,UI生命周期迁移到了scene。推送注册属于App级事务,这正是它留在原地的原因。

为什么只押注推送这一个渠道很脆弱

退一步看,一个几乎没人会打开的manifest文件里的一个key,就能在下一次发版时让你整条推送通道断掉。把这么大的风险压在单一投递渠道上,本身就很脆弱。

而这还只是推送渠道本身固有天花板之上的额外风险:opt-in授权率。即便有一套打磨过的授权请求策略,仍有相当一部分用户从一开始就不会授予推送权限,所以就算状态最好的一天,推送也只能触达你用户基数的一部分。这次平台层面的故障,只会进一步拉大”理论上能触达”和”实际触达”之间的差距。

真正扛得住这类变化的团队,都是不依赖单一渠道的团队。当推送只是push、in-app、邮件、短信等多渠道组合中的一环时,某个平台在启动阶段的一次回归,只会削弱你的整体触达能力,而不会把它归零。这也是应对Apple这类突发变更所需要的缓冲空间。

👉🏻

SDK改造正在进行时,也是个好时机回顾一下什么样的通知才真正值得发送

让Pushwoosh帮你检查iOS 27推送配置

Pushwoosh SDK已经支持iOS 27的scene lifecycle,多渠道架构意味着单一平台的故障不会拖垮你整个通知策略。如果你正在迁移过程中,某些地方注册不上,iOS SDK FAQ覆盖了常见问题。如果你想让我们帮你再核实一遍推送注册能否扛过这次迁移,我们可以和你一起过一遍集成情况——基础设施通过SOC 2 Type I、ISO 27001:2022认证,符合GDPR要求,数据中心位于欧盟和美国,出海企业的用户数据合规更有保障。

检查你的iOS 27推送配置
检查iOS 27兼容性

常见问题

不会自动出问题。已经上架的App在iOS 27设备上照常运行。真正出问题是在你用iOS 27 SDK编译新版本、却没有适配scene lifecycle的时候,这个新构建会直接无法启动。你现有的线上版本在下次提交之前都是安全的。

Pushwoosh Team
内容团队 于 Pushwoosh
分享

相关文章

查看全部