简短回答:可以,Apple 允许 Live Updates,但有范围限制。 只要更新不改变 App 的核心功能,你就可以向 iOS App 推送新的 JavaScript、HTML 和 CSS,而无需经过新的 App Store 审核。但你不能下载原生 code。
大多数困惑源于混淆了两件事:解释型 code 的 Live Updates(允许),以及下载新的可执行 code(不允许)。本文引用相关规则,说明界限在哪,并解释 Apple 如何执行这些规则。
本文不构成法律建议。这里讲的是规则目前如何表述,以及数千款 App 在生产中如何使用它们。
Apple 的规则到底怎么说
关键在两条文本。第一条是 App Store 审核指南 2.5.2:
App 应在其 bundle 内自包含,不得在指定容器区域之外读写数据,也不得下载、安装或执行会引入或改变 App(包括其他 App)功能或特性的 code。
单看这一条,像是禁令。例外在第二份文本中,即 Apple Developer Program 许可协议每位开发者都会签署的协议第 3.3.1(B) 节:
解释型 code 可被下载到 App 中,但前提是该 code:(a) 不得通过提供与提交至 App Store 时预期及宣传目的不符的功能或特性,改变 App 的主要用途;(b) 不得为其他 code 或 App 创建商店或店面;(c) 不得绕过操作系统的签名、sandbox 或其他安全特性。
所以规则不是“不能下载 code”,而是“不能下载 原生 code,且解释型 code 仅在满足三个条件时才允许”。
Capacitor App 的 Web 层是运行在 WKWebView,即 Apple 自家的 WebKit 引擎。由于 code 是解释执行的,因此属于豁免范围。指南 4.7 也明确了这一点:Apple 允许应用运行未嵌入二进制文件的 HTML5 和 JavaScript 内容。
界限在哪里
| 允许使用 live update | 不允许使用 live update |
|---|---|
| 修复 JavaScript、HTML、CSS 中的 Bug | 下载 Swift、Objective-C 或任何编译后的二进制文件 |
| 在现有 Web 技术栈中进行 UI 变更、文案修改或新增页面 | 将应用转变为完全不同的产品(例如将笔记应用变成赌场) |
| Web code 的 A/B 测试和渐进式发布 | 解锁在 App Review 期间隐藏的功能 |
| 更新图片、翻译、配置 | 构建一个可以安装其他应用或 code 的商店 |
| 回滚有问题的发布版本 | 绕过 iOS 沙盒、签名或权限 |
Capgo 仅推送左侧列的内容。它替换的是应用内的 web bundle。任何原生层面的变动,例如新增 Capacitor plugin、新增权限或 SDK 升级,仍需通过常规的 App Store 版本发布。Capgo 的 版本定向 确保需要新原生 code 的 bundle 永远不会下发到旧的二进制文件中。
Apple 如何执行规则
Apple 审核的是实质内容,而非技术手段。没人会因为通过热更新修复一个错别字而被找麻烦。Apple 针对的是那种“审核时是一个样,审核后变成另一个样”的应用。这既违反了指南 2.3.1(不得包含隐藏或未说明的功能),也违反了 2.5.2。
实际情况中:
- 只是更快地发布你原本就要提交的内容。
- 确保每次更新都符合 App Store 详情页中所描述的功能范围。
- 通过 App Review 发布重大功能更新,而使用 live updates 进行版本间的修复与迭代。
- 切勿使用 live update 来开启审核人员未曾见过的行为。
遵循这一点,Live Updates 就是发布 iOS 应用时的常规环节。React Native 团队多年来一直用 CodePush 和 Expo Updates 做这件事,Capacitor 团队则用 Capgo 来做。
哪些技术栈能在 iOS 上做到?
你的应用能否在 iOS 上通过 OTA 发布更新,取决于一个问题:应用逻辑是否在运行时解释执行?JavaScript 和其他解释型 code 可以。Swift、Objective-C 和 C++ 会编译成机器 code,不行。像 Flutter 这类经 AOT 编译的框架,只有借助解释器才符合条件。
各技术栈的 iOS live update 兼容性
| 技术栈 | code 在 iOS 上的运行方式 | 是否允许 Live Updates? | 工具链 |
|---|---|---|---|
| Capacitor | WKWebView (WebKit) 中的 JavaScript、HTML、CSS | 是 | Capgo |
| Ionic (基于 Capacitor) | 与 Capacitor 相同 | 是 | Capgo |
| Cordova | WKWebView 中的 JavaScript | 是 | 迁移至 Capacitor 后的 Capgo,旧版 Cordova 插件 |
| React Native / Expo | Hermes 或 JavaScriptCore 上的 JavaScript bundle | 支持,仅限 JavaScript | Expo Updates,自托管的 CodePush 替代方案 |
| NativeScript | 在嵌入式引擎上运行 JavaScript | 支持,仅限 JavaScript | 有限,大多为自研 |
| Flutter | Dart AOT 编译为机器 code | 仅通过解释器运行 | Shorebird (用于补丁的 Dart 解释器) |
| 原生 Swift / Objective-C | 机器 code | 原生 code 不支持;支持 WebKit 或 JavaScriptCore 中的 JavaScript | 远程配置与服务端驱动 UI |
| .NET MAUI / Xamarin | C# 经 AOT 编译为机器 code | 否 | 仅限远程配置 |
| Kotlin / Compose Multiplatform | Kotlin/Native 编译为机器 code | 否 | 仅限远程配置 |
| Unity / Unreal | C# 经 IL2CPP 或 C++ 编译 | 对 code 说不,对内容说好 | 资源包、脚本内容 (Lua) |
| Web 壳 (远程 URL) | 在 WebView 中加载的网站 | 始终实时,但存在审核风险 | 无需审核,见下文说明 |
Capacitor, Ionic 和 Cordova:完全覆盖
您应用的 Web UI 和 JavaScript 业务逻辑运行在 WKWebView中,而原生壳和插件保持原生状态。Apple 的《开发者计划许可协议》第 3.3.1(B) 节仅在下载的解释型 code 保持应用主要用途不变、不创建 code 店铺且不绕过 OS 安全的情况下才予以允许。准则 4.7 则单独管理提供 HTML5 和 JavaScript 微型应用的 App,并增加了额外要求。
live update 会替换 Web bundle。原生壳、插件和权限仍为 Apple 审核过的版本。这就是为什么 Capgo 在设计上是合规的:即使您想改,它也无法更改原生 code。新增的原生插件或权限仍需通过 App Store 发布。
React Native 和 Expo:支持 JavaScript,不支持原生模块
React Native 在 Hermes 或 JavaScriptCore 上运行您的 JavaScript bundle。该 bundle 是解释型 code,可以通过 OTA 进行更新,这正是 Expo Updates 和 Microsoft CodePush (将于 2025 年退役) 的设计初衷。 正在离开 CodePush 的团队 正在寻找替代方案。
任何对原生模块、React Native 版本或 Info.plist 原生代码的更改,都需要重新构建二进制文件。
Flutter:只能通过解释器
在 iOS 上,Flutter 会将 Dart 预编译为 ARM 机器 code. 由于禁止下载新的机器 code, 原生 Flutter 无法使用 Live Update。Shorebird 通过在解释器中运行补丁后的 Dart code 解决了这个问题,使其符合解释型 code 的豁免条件。详情请参阅 Flutter 应用的 Live Update.
Native Swift、.NET MAUI、Kotlin Multiplatform:不支持原生 code 更新
这些技术栈会生成原生机器 code. 它们无法下载替换的原生 code. 但只要符合 Apple 的条件,原生应用仍可以下载并执行解释型 code(例如通过 JavaScriptCore 运行 JavaScript)。你还可以通过 OTA 更改数据:远程配置、已审核功能的 Feature Flags、服务端驱动 UI、图片和文案。
Unity 和游戏引擎:内容可以,code 不行
游戏 code 已编译。资源包(关卡、美术、平衡性数据)属于内容,可以下载。像 Lua 这样的嵌入式脚本语言是解释型 code,遵循与 JavaScript 相同的规则。
远程 URL 封装:可热更新,但审核风险高
如果 App 只是在 WebView 里加载你的网站,那么每次部署网站时它都算被“更新”了。问题不在准则 2.5.2,而在准则 4.2(最低功能要求):Apple 会拒绝只是把网站套在框架里的 App。用 Capacitor 打包 Web App、加入原生功能并使用 Live Updates,就能避免这个问题。
iOS 上任何技术栈都做不到的事
不管用什么框架,live update 都不能:
- 下载原生 code、frameworks 或动态库
- 添加新的权限或 entitlements
- 把 App 改成 App Store 详情页未描述的内容
- 开启对 App Review 隐藏的功能
下一步该怎么做
- Android 端的回答: Google 允许无需 Play Store 审核的 Live Updates 吗?
- 执行细节: Apple 如何执行 OTA 更新限制
- 审核流程: Capacitor OTA 更新:App Store 审核指南
- 真实的被拒案例: App Store 被拒的惨痛经历
即时发布修复,无需等待审核
Capgo 实时更新 借助渠道、回滚、增量更新和端到端加密,几分钟内就能把 JavaScript、HTML 和 CSS 变更推送到你的 Capacitor 应用。它开源且支持自托管。
bunx @capgo/cli@latest init
按照 快速入门 来发送你的第一个更新。