Guideline 2.3.1:隐藏功能与 OTA 远程更新
作者:Mehdi Azizi
发布于 ,9分钟
Guideline 2.3.1:苹果拒绝任何隐藏、休眠或未写进审核备注的功能。哪些能远程修改,哪些必须重新构建,本文讲清楚。
Guideline 2.3.1 禁止隐藏、休眠或未说明的功能:你的 app 做的每件事,用户和审核员都必须看得到。每项新内容都要在审核备注里写清楚。远程更新可以修正苹果看过的东西,但永远不能添加苹果没看过的东西。
我用 Expo 发布 iOS app,每个 app 从第一个构建开始就集成了 EAS Update,用来推送修复。我的规则是:版本在审核期间,不发布任何远程更新。本指南依据的是苹果的条款原文、开发者许可协议、Expo 文档以及公开的被拒案例。
Guideline 2.3.1 说了什么?
2.3.1 是 App Review Guidelines 第 2 部分“Performance”中 2.3“Accurate Metadata”的第一条。原文开头是:“Don't include any hidden, dormant, or undocumented features in your app; your app's functionality should be clear to end users and App Review.” 说白了:不能有任何隐藏、休眠或未说明的东西。
接下来针对的是每一次提交:“All new features, functionality, and product changes must be described with specificity in the Notes for Review section of App Store Connect (generic descriptions will be rejected) and accessible for review.” 每项新内容都必须在审核备注里写清楚,并且审核员能够访问到。 所以只写“修复 bug 和改进”是不够的。
同一段还针对误导性营销:在 App Store 内外承诺并不存在的内容或服务,或者标出虚假价格。
这一部分在 Nolera 上帮了我。上线前重读商店页面时,我发现两句无法核实的话:“界面仅支持法语”,这句已经不对了;还有“最受欢迎的套餐”,可这个 app 根本还没发布过。给审核员的备注描述的也是产品的旧状态。这正是这部分条款要管的事。而 2.3.10 主要针对的是商店页面。
2.3.1 被拒消息是什么意思?
被拒标题是“Guideline 2.3.1 - Performance”,有时后面跟着“Hidden Features”。下面是 React Native 开发者公开的两条消息:
- 2024 年 3 月,在 CodePush 的一个 GitHub issue 中:“The app contains hidden features.”,没有更多说明。开发者追问细节,收到了一条依据 2.5.2 的补充:“Your app, extension, or linked framework appears to contain code designed explicitly with the capability to change the app's behavior or functionality after App Review approval.” 该 issue 没说 app 后来是否通过。
- 2023 年 2 月,在另一个 issue 中,苹果针对的是二进制包里的“CodePush-related remote code downloading/updating support”。
我的理解是:审核员看不到你的更新,他看到的是能接收更新的代码。 这两个案例针对的都是 CodePush。我没找到最近针对 EAS Update 的公开被拒案例,但这说明不了什么。
远程更新:2.5.2 和许可协议允许什么
条款 2.5.2(我在后台模式指南里也讲过)禁止 app“download, install, or execute code which introduces or changes features or functionality of the app”。
Apple Developer Program License Agreement 第 3.3.1(B) 条禁止下载可执行代码,但也写明:“Interpreted code may be downloaded to an Application”。前提有三个:这些代码不改变 app 的主要用途,不绕过签名、沙盒或系统安全机制,也不构成其他 app 的商店。
Expo app 的 JavaScript 属于解释型代码:在我看来,EAS Update 符合这个框架。它的文档提醒,更新要遵守 App Store 规则,并且“changes to your app's behavior need to be reviewed”。遇到严重 bug,Expo 建议先远程修复,再提交一个包含该修复的新版本。
我的底线:修复和调整已有的东西,可以。改变 app 是什么或卖什么,不行。
哪些能远程修改,哪些必须重新构建
这是我用的对照表,依据本文引用的苹果和 Expo 页面。“免费改为付费”这一行和最后两行是我自己的理解。
| 改动 | 途径 | 原因 |
|---|---|---|
| JavaScript bug | 远程更新 | Expo 设计的用途 |
| 文案、翻译、样式 | 远程更新 | Expo 设计的用途 |
| 订阅价格 | App Store Connect | 不改代码 |
| 推广文本 | App Store Connect | 无需重新提交 |
| 已审核类型的内购项目 | 仅审核该内购 | App Store Connect 规则 |
| 某类型的首个内购项目 | 新版本 | App Store Connect 规则 |
| 新功能 | 新版本 | 2.3.1 和 2.5.2 |
| 免费改为付费 | 新版本 | 商店页面和备注需修改 |
| 权限、Info.plist | 新构建 | 属于二进制文件 |
| 原生模块、Expo SDK | 新构建 | 原生代码变了 |
| 用于关闭功能的开关 | 远程配置 | 临时使用可以接受 |
| 用于揭示功能的开关 | 禁止 | 功能从未被审核 |
根据苹果关于版本信息的页面,推广文本可以修改“without requiring an updated submission”。根据内购提交的帮助文档,每种类型的首个内购要随新版本一起提交,之后的可以单独提交。Expo 把原生代码、权限和 SDK 版本归为需要新二进制包的改动,ITMS-90683 也是同理。用开关关掉一个出故障的功能并没有添加新东西,只要它不拿走订阅用户已付费的内容。
不被拒的远程开关
远程开关(feature flag)本身并不被禁止:关键在于它藏了什么。我的规则:
- 开关能显示的所有内容,都要对审核员存在并处于开启状态,并在审核备注里写明。
- A/B 测试比较的是两个审核员都能看到的版本,绝不是一个审核过的版本和一个没见过的版本。
- 不写任何检测审核环境、以便表现不同的代码。 这恰恰就是隐藏功能的定义。
- 远程控制的付费墙只展示已通过审核的产品,并且每个版本都遵守条款 3.1.2。
演示模式和测试账号
如果你的 app 需要登录,Guideline 2.1 要求提供演示账号,且服务器要保持在线。如果法律或安全义务使你无法提供账号,可以用内置演示模式代替,但必须“with prior approval by Apple”,而且要展示所有功能。苹果的版本信息页面还补充:账号不能过期,额外的账号写在备注里,备注上限为 4,000 字节。
和 2.3.1 的关系是:能打开平时看不到的界面的演示模式或测试账号,必须写明。 没有说明的秘密手势、暗码或特殊账号,就是隐藏功能。在备注里列出每个特殊入口以及它能打开什么。
我的规则:审核期间不发布远程更新
为什么我的每个 app 都用 EAS Update:在我的闹钟 app Solys 上,有个 JavaScript bug,只要某个音效播放失败一次,app 就会彻底没声音。修复只要三行代码,但要发出去却得完整构建一次再过一次审核。从那以后,我所有 app 从第一个构建起就集成 expo-updates:它是原生模块,没有它编译出来的二进制包永远收不到任何更新。我在 app.json 中的配置:
{
"expo": {
"runtimeVersion": { "policy": "fingerprint" },
"updates": { "fallbackToCacheTimeout": 0, "checkAutomatically": "ON_LOAD" }
}
}
使用 fingerprint 时,更新只会推送给原生代码匹配的二进制包(运行时版本)。但在版本等待审核或审核中时,我什么都不发布。原因有两个。
- 审核员看不到这个修复。 当
fallbackToCacheTimeout为 0 时,全新安装会先运行内置代码,下次启动才应用更新(expo-updates 文档)。审核员安装、启动一次,测试的是二进制包本身。Nolera 就是在修复已远程推送的情况下被拒的:我在 2.1 的指南里讲过这件事。 - 审核员测试的必须就是用户收到的。 在审核期间改变行为,会让审核看到的版本和实际交付的版本不一致。
写得具体的审核备注
2.3.1 拒绝笼统的描述。我会用的模板(英文):
What's new in this version:
- [Feature]: [where to find it, how to test it].
Remote configuration:
- "[flag]" controls [feature]. It is ON for review.
Over-the-air updates:
- The app uses expo-updates for bug fixes only.
No update will be published during this review.
Demo account: see Sign-in information. It never expires.
如何回复 2.3.1 被拒?
在 App Store Connect 里回复,在那里你可以回复 App Review 的消息。
- 消息说得含糊:问清楚针对的是哪个功能,就像 2024 年那个 issue 里的开发者那样。
- 确有某个功能描述不清:给出准确路径和一段短视频,然后补充备注。
- 某个开关揭示了未审核内容:删掉它,或者打开它并写明,然后重新构建。
- 问题出在更新代码上:说明它只用于修复,符合第 3.3.1(B) 条。在 2023 年那个 issue 里,苹果要求删除它。
提交前的 2.3.1 检查清单
- 本版本的每项新内容都在备注中写明,并附上路径。
- 每个远程开关在审核时都已开启,并在备注中写明。
- 没有任何代码针对审核员改变行为。
- 特殊入口都已说明:演示模式、测试账号、手势。
- 商店页面和付费墙没有承诺构建中不存在的东西。
- 审核期间不发布任何远程更新。
提交前检查你的材料
Review Kit 包含我的提交前检查清单、给审核员的备注模板,以及能检查你项目的 Claude Code skills。如果想要人工把关,和我预约一小时:我们一起视频检查你的审核备注和更新配置。完整流程见 app 被苹果拒绝页面。
正在准备第一次上架?做一次免费诊断:它会帮你理清步骤顺序,并发给你我的防苹果拒审检查清单。
常见问题
“Guideline 2.3.1 - Performance - Hidden Features” 是什么意思?
苹果认为你的 app 包含隐藏、休眠或没写进审核备注的功能。这条消息也可能针对能在审核后改变 app 的代码,有时会同时附带 2.5.2 被拒。
苹果允许使用 EAS Update 吗?
许可协议允许下载解释型代码,只要它不改变 app 的主要用途。修复可以,添加未经审核的功能不行。
iOS app 能用 feature flag 吗?
可以,只要它们能显示的所有内容都经过了审核。审核时打开每个开关,并在备注里写明。
演示模式需要告诉审核员吗?
需要。内置演示模式需要苹果事先同意,并且必须展示所有功能。
审核期间能发布远程更新吗?
没有条款明文禁止。但审核员第一次启动时看不到它,而且它会让实际版本和审核版本不一致。我一律不这么做。

