App Store 拒审整改实录:第三方 SDK 里藏着 UIWebView,没有源码怎么办
一次真实交付里连着吃了三道拒审:UIWebView 被弃用、部署目标不被接受、图标带 alpha 通道。最难的是第三方 SDK 没有源码。这里写清我们怎么定位、怎么改、怎么留痕。
本文只谈苹果侧(App Store)的上架与审核。文中涉及的客户一律用描述性别名,不出现客户名称。
很多甲方以为「上架」就是把包传上去、等审核通过。真正做过整条链路的人知道,开发完成只是开始——后面还有账号主体、证书与描述文件、服务端拒收、条款整改、备案材料、隐私清单一串环节,任何一环卡住,包就一直停在「可供审核」。
下面讲的不是理论,是我们一次真实交付里遇到的三道拒审。
一、一次交付里的三道拒审
那次是给一家制造业客户交付两款 App。包打得好好的,本地 Archive 完全正常,结果真正提交后连着吃了三道:
ITMS-90809——UIWebView 已被弃用。这是机审,只要包里还能扫到UIWebView的引用就会拒。code 90068——部署目标(MinimumOSVersion)不被接受。Apple 服务端直接拒收,包根本进不了审核队列。ITMS-90717——图标含 alpha 通道。1024 的商店图标必须去掉透明通道。
三道里,第二道最容易被误判成「本地打包问题」,第三道最好改,第一道才是真正的硬骨头。
二、第一道:第三方 SDK 没有源码,怎么改
UIWebView 的整改通常分三层:
- 业务代码与开源桥接层:里面的引用逐个改掉;
- 第三方二进制:某个第三方 SDK 是无源码的
.framework,里面直接引用了_OBJC_CLASS_$_UIWebView。
前两层是体力活,第三层才是分叉口。常规做法只有两条:等厂商出新版 SDK,或者把这个 SDK 换掉。前者时间不可控,后者意味着功能要重做。
我们走的是第三条路:等长替换。
需要先说明前提:这是在客户同意、SDK 厂商已不再更新的前提下采取的临时方案;有条件时应优先升级或替换 SDK。
思路并不神秘——二进制里对 UIWebView 的引用是一段固定长度的类名字符串,只要用一个字节数完全相同的自建类名替换掉它,文件大小和符号表结构都不变,签名也就不会被破坏。具体到那次操作:
- 用等长的自建类名替换
_OBJC_CLASS_$_UIWebView(替换串与原字符串长度必须完全一致); - 支持的两种 CPU 架构都要替,不能只替一个;
- 改之前对原件做 SHA256 备份,改完用
nm -u复检,确认引用数为 0。
这件事的价值不在「技术多炫」,而在于它把一次不可控的外部依赖,变成了自己能把控的一件事。这种改法需要对 Mach-O 符号表有足够把握,也要承担升级该 SDK 时重新检查的成本——这一点我们在交付文档里写清楚了。
三、第二道:只有真上传才会暴露的错误
code 90068 的错误信息很直白:MinimumOSVersion '12.0' is not acceptable。
麻烦在于它是 Apple 服务端返回的。本地 Archive、导出、校验全部通过,Xcode 不会给你任何提示,只有真正执行上传那一步才会被拒。也就是说,没实际传过包的团队,往往要等到客户催进度时才发现。
处理办法本身不复杂:把部署目标从 12.0 提到 15.0,重新归档上传。但它说明一个道理——有一类问题只能靠真机真传才能发现,本地自测测不出来。所以我们在交付里把「上传」作为独立环节,并保留上传回执,便于定位卡在哪一步。
四、第三道:图标的 alpha 通道
ITMS-90717 是三道里最轻的,但也最常犯:设计给的 1024 商店图标带透明通道,导出时没压平。
改法就是重新导出一张不含 alpha 的 1024 图标。它单独看不值一提,但和前两道放在一起,正好说明上架卡点的分布特征:既有无源码 SDK 这种硬骨头,也有纯流程疏忽。所以我们在交付里配了合规自检脚本,把 UIWebView 扫描和隐私清单检查做成可重复执行的动作。
五、拒审之外,真正拖时间的往往是账号与主体
三道拒审都过了之后,复盘下来,真正容易导致项目停摆的其实在更前面:
- 主体不一致:开发者账号主体、合同甲方、历史证书里的 O 字段三者可能对不上。它不会立刻报错,但会在签名主体与后续交接时变成争议。我们在项目启动时就把这一项列为必查。
- 许可协议未接受:企业账号如果没接受新版开发者许可协议,是无法提交的——这也是一个「不提交就发现不了」的坑。
- 描述文件与分发通道:App Store 描述文件和 Ad Hoc 描述文件是两回事,Ad Hoc 只能用于有限台设备的内测分发(按 UDID 白名单),不能上传到 App Store Connect。想两条通道都要,就得两套都配。
六、上架前,我们建议甲方照着核对的清单
结合那次交付,把可复用的一列出来:
- 账号与主体:账号是否已核验、许可协议是否已接受、账号主体与合同主体是否一致。
- 证书与描述文件:发布证书是否由本次交付生成并在有效期内;App Store / Ad Hoc 两套描述文件是否分别配置。
- 依赖层扫描:业务源码与桥接层的
UIWebView引用清零;第三方二进制是否做过等长替换并留备份与复检记录。 - 部署目标:确认满足 Apple 当前的最低要求,别等到服务端拒收再返工。
- 资源检查:1024 图标无 alpha 通道。
- 隐私合规:
PrivacyInfo.xcprivacy是否齐备,Required Reason API(UserDefaults、文件时间戳、系统启动时间、磁盘空间)是否逐条声明;隐私政策页与审核备注、权限用途说明是否齐备。 - 特殊配置:Universal Links(AASA)里的 Team ID 是否与当前证书一致;ATS 是否按域名白名单收紧。
- 签名防回归:CocoaPods 子工程的签名配置容易在
pod install后被冲掉,需要在post_install阶段固定住。 - 备案材料:材料清单、备案信息表、用于备案的证书公钥与指纹是否齐备,并确认备案号在 App 内的展示位置(通常在「设置→关于」)。
- 上传留痕:用脚本化的签名上传链路执行(校验→归档→导出→上传),保留每次回执。
七、还有一种拒审,改代码没用
除了机审类错误,还有一类主观条款拒审,典型的是 4.3 条款(Spam / 同质化)。我们处理过一次 4.3 条款的整改上架——这类问题的解法不在代码里,而在于能不能拿出产品差异化的证据链:功能定位、目标人群、内容结构、与已有产品的区别,都要能讲清楚并体现在商店页与审核备注里。
写在最后
上架这件事,难点不是「会不会点上传按钮」,而是能不能在提交前把那些只有真传过才会暴露的问题提前找出来,以及遇到无源码依赖时有没有不换 SDK 的第三条路。
我们提供从账号核验、证书与描述文件、归档上传、拒审整改,到备案材料与隐私清单的完整上架服务。包已开发完却卡在上架环节,可以把拒审信息发给我们做卡点判断。
了解更多:App 上架与发布服务 · 原生 App 开发 · 技术咨询
需要一份写清范围的清单?把你的业务说一遍,几秒生成需求清单。