Anthony Bourdain 关于“周一别点鱼”的著名警告,其实是在警告菜单背后的供应链。在 1999 年的那篇文章里,他描述了周末采购的海鲜到了周一还在被端上桌。知道厨房怎么运转,改变了他点菜的方式。
开发者也有类似的规则:永远不要在周五发版。一旦出事,你就等于替所有人安排好了周末。这是极好的建议,而我们多年来一直无视它。
![]()
我们之所以选择周五发版,是为了避免把框架更新丢进客户发版周的中间。想法是利用这个相对安静的窗口,在和客户的发布正面相撞之前,先测试和修复我们自己的版本。这个假设建立在对别人何时发版的推测之上。它也让我搭进去了大量周末。
这一次,问题吞掉了这一周的大部分时间。可选的 Xcode 27 路径、推送、窗口相关功能接连出现回归。有些问题彼此重叠,所以一个修复可能让原本的症状消失,底层问题却在别处重新冒出来。这对我们来说已经够累,对正赶着完成应用的客户来说更糟。
一次被拒,暴露的不止一个坑
其中一个失败案例特别有教育意义。使用了特定加密 API 的应用,可以编译、链接、运行,却在提交 App Store Connect 时被拒,原因是二进制文件导入了非公开符号。大多数应用并没有启用受影响的代码,所以大多数用户和我们常规的测试从未遇到它:
- _CCCryptorGCMAddAAD
- _CCCryptorGCMAddIV
- _CCCryptorGCMFinal
SDK 的库导出了这些符号。链接器能解析它们。但它们并没有在公开头文件中声明。链接成功,并不等于这些符号是适合提交的公开 API。
我们的测试还有另一个盲区:这些调用藏在一个可选功能开关后面,示例应用默认并不启用。就算把同一个示例测更多遍,那个分支也不会出现。
新的检查器源码保留了这次被拒的记录和因果链。比起一句“我们加了更多测试”,这是一份更有用的记录。
一个字符串替换,牵出一条隐藏路径
构建器启用功能的方式,是替换一个预处理器定义。对某个名字做纯字符串替换时,也匹配到了一个更长名字的开头:
- #define CN1_INCLUDE_CRYPTO
- #define CN1_INCLUDE_CRYPTO_GCM
于是,启用通用加密,可能顺带把 GCM 路径也启用了。客户并不需要主动请求那条可选路径,就会编译到相关导入。
修复方案是让构建器匹配完整的定义名。一个针对性的回归测试守住这个行为。GCM 实现本身被重写,改用公开的 CommonCrypto 操作,以及 GHASH 这个认证计算的 C 实现。认证解密路径会在释放解密输出之前先验证标签。
另一次排查还发现了 sqlite3_key 和 sqlite3_rekey 的导入问题:它们对捆绑的加密引擎有效,却不是苹果 SQLite 头文件中的公开声明。现在这些导入依赖捆绑引擎来定义它们。一次被拒的上传,暴露出不止一处把“符号能链接”当成了“符号是公开的”。
新闸门怎么问这个问题
针对那三个名字的回归测试,只能抓住这三个名字再次出现。我们还需要检查更广泛的错误:导入了一个没有公开声明的 SDK 符号。
新的闸门用两种方式提出这个问题:
检查器利用 SDK 导出信息识别库符号,并扫描去掉注释后的公开头文件。出现在注释里的名字不算声明。它还会先跑已知的私有和公开对照样本,确认检查器能报错,再信任一次干净的结果。一个永远不会报失败的检查,是一种让人很安心却什么也学不到的方式。
要在本地检查一个已链接的应用,脚本提供了二进制模式:
- python3 scripts/check-ios-private-api.py \
- --binary /path/to/Release-iphoneos/MyApp.app --sdk iphoneos
把示例路径换成你自己的 Release 设备产物;如果 SDK 属于另一个 Xcode 安装,用 --developer-dir 指定。仓库已经把源码模式和 Release 二进制模式接入了 iOS 检查。PR #5923 包含了实现和构建器修复。
这是一项针对已覆盖的 C 和 Objective-C 表面的导入符号检查。它会跳过这个头文件扫描无法匹配的 Swift 和 C++ 修饰名,也不检查私有的 Objective-C 选择器。仓库里的二进制检查跑的是已覆盖的示例和捆绑组件,而不是客户可能添加的每一个任意依赖。
它也无法预测每一次 App Store 审核决定。授权、隐私声明和应用行为是另外的问题。检查导入,能在你上传之前抓住一个具体的被拒理由。
准备提交时,请单独审查其余那些检查。
这一周很难。我花了太多时间追回归问题,你们中一些人也花了时间向我们证明,一个修复并没有真正修好你的问题。那本该是你们能花在自己应用上的时间。我们的发布流程需要做得更好。
改变我们发版的日子,并不能修补让这些失败溜过去的缺口。我们的计划是加强围绕交付物的闸门:跑可选配置、检查最终产物、测试能覆盖一整类错误的规则。我们仍然需要为每个 bug 配一个小回归测试。我们也需要问:为什么在检查全绿的情况下,那个 bug 还能到达客户手里。
私有 API 闸门是这个计划中具体的一步。它检查我们常规示例漏掉的功能组合,检查一个已链接的 Release 应用,并用已知的对照符号验证检查器确实能失败。这周……
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.