← 全部开发日记
2026-09-16 · 上架踩坑

被 App Store 打回的那几次,和我改的东西

ITMS-90683、bundle id 撞车、恢复购买、外链风险、导出合规——逐条贴报错码和 Info.plist 键名,附一份可复制的提审前 checklist。

ITMS-90683。

看到这串东西的时候,我以为我完了。

那时候我已经把包传上去了,正准备去洗澡,邮件弹出来,标题是「App Store Connect: Your app has one or more issues」。我站在原地把邮件读了三遍。

后来才知道这玩意儿其实很小。但当时不知道,那种「我是不是做错了什么根本性的事」的感觉,我想很多第一次提审的人都懂。

这篇把我踩到的坑逐条写下来,贴真实报错码和真实键名。写得越具体越有用,所以你会看到一堆代码片段,忍一下。

配图Xcode / 邮件里的 ITMS-90683 报错截图

先声明一件事

我这篇里的「被打回」,严格说分两种:

区分这个很重要——自动校验的打回,秒级到分钟级就来了,改完重传就行,不占审核队列。看到 ITMS 别慌,那不是「被拒」。

我这次的首审实际用时:【待填:首审实际耗时】

一、ITMS-90683:缺 NSLocationWhenInUseUsageDescription

现象:包上传后自动校验失败,邮件退回。

报错原文(大意)ITMS-90683: Missing purpose string in Info.plist —— 你的 App 引用了 NSLocationWhenInUseUsageDescription 相关的能力,但 Info.plist 里没有对应的用途描述字符串。

我的第一反应:不可能。我这 App 是管冰箱的,我根本没写过一行定位代码。

真相:我确实没写。但我用的相册/文件选择组件,链进来的系统接口里带了定位能力。苹果的自动校验只看「你的包里链进了什么」,不看「你的 Dart 代码有没有调」。

我改了什么:在 ios/Runner/Info.plist 里补上,并且如实说明我不用它

<key>NSLocationWhenInUseUsageDescription</key>
<string>本 App 不使用定位功能。此项仅因所用的相册/文件选择组件依赖系统接口而被要求声明,我们不会获取或上传你的位置信息。</string>

然后版本号 bump 到 1.0.0+3,重新构建上传。过了。

教训

凡是链接进包的能力都要有用途描述,哪怕你的代码从没主动调用过。

第三方 SDK 带进来的权限也算。你以为自己没用,链接器不这么认为。

顺带一句:用途描述别写敷衍话。我这条把「为什么明明不用还要写」解释清楚了,人工审核那关也少一次问询。同一个文件里我还有:

<key>NSPhotoLibraryUsageDescription</key>
<string>用于从相册选择购物小票或食材照片,自动识别并入库到冰箱</string>

具体到哪一句话对应哪个功能,比「用于提供更好的服务」强一百倍。

配图Info.plist 权限描述段落截图

二、bundle id 撞车,我改了两次包名

这条不是被打回,是我自己给自己挖的坑,但坑得很典型。

现象:在开发者后台注册 App ID,填 com.chihaofan.app,提示不可用。

原因:这串被我此前另一个免费账号在 Apple 侧占用了。我换成 com.chihaofan.ice——还是不可用

这里是关键认知:App ID 在 Apple 全局命名空间唯一,跨所有开发者账号。不是「你的账号里没有就能用」,是「全世界没人用过才能用」。

我改了什么:换成自己域名的反写——top.chihaofan.app。域名 chihaofan.top 是我的,这串只有我能注册,杜绝再撞。

连带要改的东西:

最疼的一个坑:安卓那边,旧包名和新包名在系统眼里是两个不同的应用。手机上装过旧包的,装不上新包,必须先卸载。我自己的测试机就这么中招过一次,卡在那儿研究了半天为什么「安装失败」。

教训

包名在注册前先去后台试着注册一次,别等到写完代码才发现要改。 反写自有域名是最省事的防撞方案——注意,苹果和谷歌都不校验你是否真的拥有该域名,但拥有它能保证别人不会跟你想到一块去。

补一句:包名用户看不见,防不了山寨。真要防山寨得靠名称占位 + 注册商标(第 9/42 类),有商标才能走 App Store Content Dispute 通道。

三、iPhone-only:TARGETED_DEVICE_FAMILY

现象:一开始设备支持范围是通用(iPhone + iPad)。结果在 iPad 上,手机 UI 居中、两边补大片灰边,非常难看。

我改了什么ios/Runner.xcodeproj 里把 TARGETED_DEVICE_FAMILY"1,2" 改成 1SUPPORTED_PLATFORMS = iphoneos

改完有两个好处:

  1. ASC 不再要求 iPad 截图——少一整套素材要做。
  2. iPad 上系统把它当手机应用跑(画布 < 600pt,我 lib/app.dart 里那段补灰边逻辑压根不触发),反而没有灰边;台前调度下还能变成一个可拖拽的手机窗口,体验更好。

这是我这次最蠢的一次返工:我先花时间做完了一整套 iPad 截图(还专门写了个排版脚本),做完才决定改 iPhone-only。那批图现在躺在 design-spec/appstore_shots/ipad-13/ 里,一张也没用上。

教训

先定设备支持范围,再做截图。 顺序反了,返工的是最费时间的美术活。

四、内购必须有「恢复购买」入口

这条是苹果必查项,没有会被拒,属于「不需要有人告诉你、你就应该知道」的那类。

我改了什么:设置页放一张「完整版」状态卡,卡里有「恢复购买」按钮。付费墙半屏底部也有一个。

技术上就是调一次 restorePurchases(),把商店里这个 Apple 账号名下的非消耗型商品捞回来、重新落解锁态。

顺带说我的解锁态设计:存在 Hive(proUnlocked + ownedPaidSkins),不进导出同步码

原因很实际:吃好饭有个「导出同步码」功能给家人同步冰箱和账本用。如果解锁态跟着同步码走,那这串码就是破解码,一个人买了全家甚至全网都能用。换机恢复解锁,走「恢复购买」,那是商店的活。

教训

内购三件事一起做:购买 → 恢复购买入口 → 卸载重装后能恢复。 少任何一个都会被打回,第三个还得真机验过才算数。

配图设置页「完整版」状态卡 + 恢复购买按钮截图

五、外链风险:我把协议搬进了 App 里

现象:一开始「功能介绍 / 用户协议 / 隐私政策」我打算全部外链到官网,App 里放几个跳转按钮。

为什么改:苹果对 App 内跳外部网页这件事一直敏感(尤其涉及支付引导时,3.1.1 那条)。我的协议页本身无害,但我不想为了省事去赌审核员当天怎么理解。

我改了什么:把三份文档做成 App 内置的 Markdown 常量,放在 lib/features/game/about_docs.dart,App 内直接渲染,不跳浏览器。ASC 那边要填的隐私政策 URL 照旧指向官网(那是硬要求,必须有公网 URL)。

代价,说实话同一份内容两处维护。官网 HTML 一份,App 内 Markdown 一份。改任何一处都要同步另一处,包括底部的更新日期。

我现在还没有好办法,靠的是每次改的时候自己记得。这是我留给未来自己的一颗定时炸弹,写在这里也算立个字据。

教训

法务文本在 App 内可读、不依赖网络,是最稳的做法。 但要提前想好两处怎么同步——最好一开始就从同一份源文件生成两份产物,别像我一样手动抄。

六、导出合规:ITSAppUsesNonExemptEncryption

现象:每次提交都被问「你的 App 是否使用加密」,答一次问卷。

我改了什么:Info.plist 里直接声明掉:

<key>ITSAppUsesNonExemptEncryption</key>
<false/>

我只用系统 HTTPS,属于豁免范围。声明一次,以后每次提审不再答题。

教训:能在 Info.plist 里一次性声明的,就别留给每次提交的手工操作。手工操作总有一天会点错。

七、几件顺手做了、但没做会挨打的事

onboarding 与购买流的边界。我修过一批边界情况,典型的是「引导流程还没走完就能摸到付费墙」。这种路径审核员很爱走,因为它就是「乱点」。改法很土:付费墙入口统一加前置判断,引导没完不给进。

启动屏。做了品牌着色的启动屏。这个不改也能过,但一个纯白闪屏会让整个 App 显得没做完。

评分引导。接了 RatingServicelib/services/rating_service.dart),在「高兴时刻」触发系统评分请求——采购全部完成之后、用餐打卡成功之后。

这里有个硬约束要记住:苹果的评分 API 一年最多真正弹 3 次,你申请了不弹是正常的。所以时机必须挑最爽的那一下,不能上来就问。我自己还加了一层节流:累计打卡 ≥ 5 次才开始问,两次间隔 ≥ 60 天。

沙盒测试。提审前必须在 ASC 建沙盒测试员账号,真机登录,走完整四步:

购买 → 解锁 → 卸载重装 → 恢复购买。

这四步任何一步断了,上线之后就是真金白银的差评。模拟器测不了,必须真机。

八、钱的部分

Apple Developer 个人账号 ¥688 / 年。到期不续,所有已上架 App 会下架(数据保留),记得设自动续费。

还有一件必须主动去点的事:Small Business Program。上一自然年全部 App 收入 ≤ 100 万美元就符合条件,申请通过后苹果抽成从 30% 降到 15%

不申请就默认 30%。这不是自动的。

而且要注意顺序:签《付费应用协议》→ 填 W-8BEN 税表 → 绑银行卡 → 申请 SBP,这一整套要在第一笔销售之前办完,否则前面卖的那些就按 30% 抽了。

提审前 checklist(可以直接抄走)

Info.plist

标识符

内购

显示与素材

合规文本

账号与钱

流程边界


最后说句实在的。

被打回不丢人,ITMS 那一串数字看着吓人,本质上只是苹果的机器在提醒你少填了一个字段。真正费时间的从来不是改这些,是顺序搞错导致的返工——比如我那批白做的 iPad 截图。

所以这份 checklist 的价值不在于「有哪些项」,在于「哪些项必须先做」。

吃好饭 iOS 已上架,App Store 搜「吃好饭 AI冰箱」。这个系列会一直写下去,欢迎在留言区骂我。