ARTICLE · 1350929
iOS过审不是靠运气:一个生活服务App上架前踩过的8个坑
很多团队第一次做 iOS 上架,会把它理解成“打个包、传到 App Store Connect、等审核”。
真正跑一遍才发现,苹果审核看的不是单一功能,而是整套产品事实是否一致:App 怎么登录、核心功能是什么、为什么需要这些权限、有没有账号注销、支付对应实物还是虚拟权益、第三方 SDK 有没有如实披露、审核员能不能真的走完全流程。
iOS 的规范还是要详细看完核对好再申请上线,因为一旦不过,后面审核就越来越严格。宁愿把提交时间往后压,也不要带着“先试一下”的侥幸心态进审核队列。

这篇复盘的是一个生活服务类 Flutter App:内容浏览、实物商城、本地生活、任务积分都在一个客户端里。下面这些坑,有的已经造成补材料,有的在提审前被及时拦住。它们都不神秘,但每一个都足够拖慢一次发版。
一、第一次提审被要求补材料,2.1 不一定是硬拒
第一次提审后,苹果返回了 Guideline 2.1 - Information Needed。
这类问题的重点是“信息不足”,不一定意味着功能违规,也不一定要重新传包。审核员通常希望你进一步说明:
App 的用途、目标用户和主要功能; 如何登录,是否提供审核账号; 核心业务流程怎么操作; 是否支持账号注销; 使用了哪些外部服务和第三方 SDK; 是否存在地区差异、受限行业或版权内容。
如果只回复“我们的 App 可以正常使用”,基本没有帮助。审核员需要的是清晰、可执行、可验证的信息。

更好的做法是在提审前准备一份“审核员材料包”:
1. 一段清楚的 App 定位说明;
2. 稳定的审核账号和登录步骤;
3. 真机录屏,从 App 图标冷启动开始;
4. 覆盖登录、内容浏览、商城、本地生活、支付拉起、账号注销等主链路;
5. 第三方 SDK 清单及用途;
6. 英文审核备注,避免审核员猜流程。
录屏不要只录 happy path。账号注销、重新注册、异常返回、支付取消、权限拒绝后的表现,都应该提前验证。
二、审核账号能登录,不等于审核员能按你的步骤登录
最典型的一个坑,是短信登录的审核账号。
App 当时只保留验证码登录。审核员无法接收发到指定手机号的真实短信,于是后端为审核手机号配置了固定验证码。
研发本地测试时,固定验证码确实有效。但审核员操作时多了一个很自然的动作:点击“获取验证码”。
这一下就暴露出问题:
有的实现是“不点发送才能通过”; 有的是随机验证码覆盖了固定验证码; 有的是校验接口只认缓存里的随机码; 还有滑块验证、发送频控、注销后重建等链路没有一起放行。
结果就是:我们以为账号可用,审核员却登录失败。

不要在审核备注里要求审核员“请不要点击获取验证码”。这不符合正常用户路径,也很容易再次翻车。
正确方案是二选一:
白名单手机号点击发送后,短信通道真的下发固定验证码; 或者校验接口对白名单手机号无条件接受固定验证码,不受随机码状态影响。
验证时必须严格模拟审核员路径:输入手机号 → 点击获取验证码 → 完成滑块 → 输入固定验证码 → 登录 → 注销 → 用同一号码重新注册。
审核账号不是“能登录一次”就够,而是要能被陌生人按照页面提示顺利操作。
三、积分、提现、赚钱文案,是 iOS 的高敏区
这个 App 有任务积分体系,用户完成内容浏览、签到或任务后获得积分,部分业务还涉及提现。
在安卓环境里,团队可能更关注功能是否闭环;到了 iOS,产品表述必须更谨慎。“看内容得奖励”“积分可提现”“赚钱”“红包”等文案,容易让审核员关注 Guideline 3.2 相关风险。

这里不能简单理解成“把按钮藏起来”。苹果审核看的是完整产品形态:
App Store 应用描述怎么写; 截图里有没有提现、赚钱表达; 首页有没有奖励浮层; 我的页面有没有提现入口; 账号与安全里有没有收款、实名认证相关模块; 协议列表里有没有提现规则; WebView 标题、通知、深链、历史记录里会不会露出相关内容。
当时的处理方式是为 iOS 增加独立功能开关,并且默认隐藏高风险入口,包括提现按钮、奖励浮层、收款设置、实名认证模块及相关协议入口。开关接口失败、字段异常或客户端状态不完整时,也按隐藏处理。
同时统一产品口径:不强调赚钱,不承诺固定收益,把积分表述为会员积分或会员权益,强调实物电商和本地生活服务,说明第三方支付对应的是实物商品或线下服务,而不是购买可提现的虚拟资产。
这不是靠文案“钻空子”,而是让 iOS 版本真正呈现一套适合 App Store 规则的功能集合。产品、运营、研发、法务和提审材料必须使用同一套口径。
四、微信回跳失败,往往不是微信 SDK 一个地方的问题
另一个很耗时的坑,出现在微信授权和 Universal Link 上。
现象是:用户从 App 拉起微信,授权完成后没有正常回到 App,而是跳到一个 H5 页面,甚至出现 Safari 与 App 之间反复弹回。

Universal Link 至少涉及五个环节:
1. 微信开放平台登记的 Universal Link;
2. App 客户端配置的 Universal Link;
3. 服务端提供的 AASA 文件;
4. iOS 工程的 Associated Domains 能力;
5. App 收到回跳后的实际处理逻辑。
任何一个地方不一致,都可能失败。例如:
AASA 里的 Team ID 或 Bundle ID 与当前签名不一致; AASA 路径没有覆盖微信实际回跳地址; Debug 包需要开发者模式验证,Release 包又走 Apple CDN 缓存; 微信开放平台仍是旧域名,客户端已经换成新域名; 服务端 AASA 已更新,但 CDN 还没刷新; App 能被拉起,却没有正确消费授权链接。
这里还有一个容易忽略的生命周期问题:当 App 使用 Scene 生命周期后,系统不一定再把 Universal Link 事件交给 AppDelegate。
冷启动和热启动的入口也不同:App 未运行时,回跳信息可能在 Scene 的 willConnectTo 中;App 在后台时,才可能通过 continue 或打开 URL 的回调进入。只实现热启动处理,会漏掉冷启动授权。
所以不能只测“App 已经打开时能不能回来”。必须覆盖未启动 App、App 在后台、Debug、Adhoc、Release 三种包,以及授权后取消、重复授权、网络异常和登录态失效。
五、隐私合规不是补一段政策,声明必须和代码一致
隐私问题最忌讳“政策里写一套,代码里做另一套”。
这次排查中就发现过明显不一致:隐私政策曾经写着不使用第三方支付,但工程实际上已经接入了微信 SDK,用于实物商城支付、授权或收款确认。
这种问题一旦被审核员发现,很难解释成技术疏忽。

iOS 侧重点核对四类内容。
第一是 IDFA 和 ATT。只要用于广告归因或跨 App 追踪,就需要先通过 ATT 获取授权。用户拒绝后,不能把全零 UUID 当成有效 IDFA,后端也要接受空值。ATT 弹窗不能早于自家隐私协议。
第二是权限文案。相机、相册、定位、通知等权限,需要说明具体使用场景,不能过于笼统,也不能诱导用户授权。
第三是隐私清单。需要结合 App 实际使用的 Required Reason API、追踪状态、追踪域名和第三方 SDK 情况核对。不是文件存在就代表合规,里面的理由必须能对应真实代码。
第四是 SDK 初始化时机。用户同意隐私政策前,不应提前初始化追踪、统计、性能监控或推送相关能力;用户拒绝后,也不能继续采集。
建议每次提审前同时做三件事:依赖清单扫描,确认实际接入了哪些 SDK;运行时抓包验证,确认同意前、拒绝后有没有数据发出;隐私政策校对,确认 SDK 名称、用途、收集字段、第三方主体是否一致。
隐私政策更新后,还要同步更新版本标识。否则老用户已经同意过旧版本,不会重新看到新的确认弹窗。
六、测试配置进入 Release,是最不值但最常见的事故
移动研发早期为了联调方便,经常会在工程里保留测试配置。这个项目也不例外,例如测试环境开关、测试证书兼容、TLS 主机名放行、隐藏环境切换入口、调试日志等。

如果这些配置进入正式提审包,就会出现很被动的问题:
App 默认请求测试环境; 测试证书不匹配,却被客户端放行; 设置页可以切换环境; 日志里打印 token、手机号或用户标识; 固定验证码、测试支付参数还在生产包中; 内部域名、IP、调试入口可以被扫描出来。
这类问题不应该只靠提醒,而应该通过构建配置强制隔离。Release 包应显式使用生产环境、禁止运行时环境覆盖、关闭测试 TLS 放行、关闭环境切换入口、关闭敏感调试日志,并不保留固定验证码和测试支付参数。
正式包构建完成后,还要检查产物中的实际配置,而不是只看代码。比如确认接口地址、ATS 策略、权限描述、隐私清单、SDK 列表和字符串内容。
一句“我记得打正式包时加了参数”是靠不住的。要让流水线在发现测试配置时直接失败。
七、底栏顺序和 WebView 风控,容易暴露跨端细节
有两个看似不大的问题,很能体现上线检查是否细致。
第一个是底栏顺序。为了让 iOS 用户首先看到商城和本地生活,团队调整了底部 Tab 顺序。但页面数组、索引常量、底栏文案和可见性判断最初没有全部从同一个来源生成,导致部分页面的位置和显隐状态错位。
这类问题的根因是“同一个事实被写了多份”。正确做法是把 Tab 顺序作为唯一事实源,页面、文案、索引、埋点和可见性判断都从同一个枚举或配置生成。改一次顺序,不应该要求开发者手动改四五个文件。
第二个是 WKWebView 里的风控验证。验证码 SDK 对 JS 加载和验证请求之间的时间有要求。安卓 WebView 相对慢一些,固定延迟可能刚好满足;iOS WKWebView 执行更快,同样的延迟就可能低于风控时间窗,导致验证失败。
这说明不能简单用安卓现象推断 iOS,也不要写死跨端延迟。更稳妥的方式是记录 SDK 加载时间,动态计算还需要等待多久,既满足风控要求,也不影响慢设备上的体验。
类似问题还包括小屏模式、字体放大、系统版本差异、冷启动时序和弱网场景。它们不一定每个都会造成拒审,但会影响审核员对 App 完成度的判断。
八、提审通过只是开始,版本还要能灰度、能暂停、能回退
iOS 上线不能只盯着“有没有通过审核”。
如果版本发布后出现登录异常、支付问题、启动崩溃或第三方服务故障,而客户端没有远程控制能力,就会非常被动。
提审前至少要准备:
TestFlight 主链路回归; 最低版本和强制更新机制; 远程功能开关; 关键模块一键降级或停用能力; 崩溃、卡顿、接口错误监控; 灰度放量计划; 服务端回滚和数据兼容方案; 审核通过后分阶段开放,而不是一次性全量。
尤其是支付、积分、任务、推送、登录等模块,要提前确认开关粒度。不能只准备一个“杀掉整个 App”的总开关。
正式提审前,也不要向业务方承诺具体上架时间。提交时间可以控制,审核时长和审核反馈不可控。项目计划里应该预留审核补材料和修复轮次。
最后的一张上线检查表
如果只记住一份清单,可以在每次 iOS 提审前核对下面这些项:
iOS 过审的本质,不是把功能“包装”成合规的样子,而是让产品形态、技术配置、隐私声明、提审素材和运营预案保持一致。
真正可靠的上线能力,来自提审前的证据、默认安全的配置,以及出现问题后可以快速收回的开关。
少依赖临场发挥,少重复提交,把问题前置,才是一个团队 iOS 发布能力成熟的开始。
往期
《意识型面试题:我们更换了证书,某行没换导致问题怎么解决?》
《编写代码的「八荣八耻」- 以开关上线为荣,以自信编码为耻》
—— 静姐 / 编程一生















































































