Warning: Attempt to read property "post_author" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/opengraph/class-facebook.php on line 325

Warning: Attempt to read property "post_author" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/opengraph/class-facebook.php on line 330

Warning: Attempt to read property "post_date" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/opengraph/class-facebook.php on line 311

Warning: Attempt to read property "post_modified" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/opengraph/class-facebook.php on line 312

Warning: Attempt to read property "ID" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/modules/schema/class-opengraph.php on line 67

Warning: Attempt to read property "ID" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/modules/schema/class-opengraph.php on line 77

Warning: Attempt to read property "ID" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/modules/schema/class-jsonld.php on line 139

Warning: Attempt to read property "post_modified" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/modules/schema/class-jsonld.php on line 757

Warning: Attempt to read property "post_date" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/modules/schema/class-jsonld.php on line 758

Warning: Attempt to read property "post_type" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/modules/schema/class-jsonld.php on line 766

Warning: Attempt to read property "post_author" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/modules/schema/class-jsonld.php on line 772

Warning: Attempt to read property "post_modified" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/modules/schema/class-jsonld.php on line 775

Warning: Attempt to read property "post_date" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/modules/schema/class-jsonld.php on line 775

Warning: Attempt to read property "post_content" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/modules/schema/blocks/class-block-parser.php on line 73

Warning: Attempt to read property "ID" on null in /usr/local/lighthouse/softwares/wordpress/wp-content/plugins/seo-by-rank-math/includes/modules/schema/class-frontend.php on line 71
技术笔记

App Store 拒审整改实录:第三方 SDK 里藏着 UIWebView,没有源码怎么办

一次真实交付里连着吃了三道拒审:UIWebView 被弃用、部署目标不被接受、图标带 alpha 通道。最难的是第三方 SDK 没有源码。这里写清我们怎么定位、怎么改、怎么留痕。

2026 年 9 月 26 日 · 技术笔记

本文只谈苹果侧(App Store)的上架与审核。文中涉及的客户一律用描述性别名,不出现客户名称。

很多甲方以为「上架」就是把包传上去、等审核通过。真正做过整条链路的人知道,开发完成只是开始——后面还有账号主体、证书与描述文件、服务端拒收、条款整改、备案材料、隐私清单一串环节,任何一环卡住,包就一直停在「可供审核」。

下面讲的不是理论,是我们一次真实交付里遇到的三道拒审。

一、一次交付里的三道拒审

那次是给一家制造业客户交付两款 App。包打得好好的,本地 Archive 完全正常,结果真正提交后连着吃了三道:

  1. ITMS-90809——UIWebView 已被弃用。这是机审,只要包里还能扫到 UIWebView 的引用就会拒。
  2. code 90068——部署目标(MinimumOSVersion)不被接受。Apple 服务端直接拒收,包根本进不了审核队列。
  3. 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。想两条通道都要,就得两套都配。

六、上架前,我们建议甲方照着核对的清单

结合那次交付,把可复用的一列出来:

  1. 账号与主体:账号是否已核验、许可协议是否已接受、账号主体与合同主体是否一致。
  2. 证书与描述文件:发布证书是否由本次交付生成并在有效期内;App Store / Ad Hoc 两套描述文件是否分别配置。
  3. 依赖层扫描:业务源码与桥接层的 UIWebView 引用清零;第三方二进制是否做过等长替换并留备份与复检记录。
  4. 部署目标:确认满足 Apple 当前的最低要求,别等到服务端拒收再返工。
  5. 资源检查:1024 图标无 alpha 通道。
  6. 隐私合规:PrivacyInfo.xcprivacy 是否齐备,Required Reason API(UserDefaults、文件时间戳、系统启动时间、磁盘空间)是否逐条声明;隐私政策页与审核备注、权限用途说明是否齐备。
  7. 特殊配置:Universal Links(AASA)里的 Team ID 是否与当前证书一致;ATS 是否按域名白名单收紧。
  8. 签名防回归:CocoaPods 子工程的签名配置容易在 pod install 后被冲掉,需要在 post_install 阶段固定住。
  9. 备案材料:材料清单、备案信息表、用于备案的证书公钥与指纹是否齐备,并确认备案号在 App 内的展示位置(通常在「设置→关于」)。
  10. 上传留痕:用脚本化的签名上传链路执行(校验→归档→导出→上传),保留每次回执。

七、还有一种拒审,改代码没用

除了机审类错误,还有一类主观条款拒审,典型的是 4.3 条款(Spam / 同质化)。我们处理过一次 4.3 条款的整改上架——这类问题的解法不在代码里,而在于能不能拿出产品差异化的证据链:功能定位、目标人群、内容结构、与已有产品的区别,都要能讲清楚并体现在商店页与审核备注里。

写在最后

上架这件事,难点不是「会不会点上传按钮」,而是能不能在提交前把那些只有真传过才会暴露的问题提前找出来,以及遇到无源码依赖时有没有不换 SDK 的第三条路。

我们提供从账号核验、证书与描述文件、归档上传、拒审整改,到备案材料与隐私清单的完整上架服务。包已开发完却卡在上架环节,可以把拒审信息发给我们做卡点判断。


了解更多:App 上架与发布服务 · 原生 App 开发 · 技术咨询

需要一份写清范围的清单?把你的业务说一遍,几秒生成需求清单。