☰
旧版Flutter项目Xcode 14上架报bitcode错误:完整排查与修复指南
2026/10/3 3:29:09 网站建设 项目流程

前阵子有朋友找我处理一个压了两年的 Flutter 老项目,要上架新版本。项目本身是 Flutter 1.22 时代的产物,本地 Xcode 已经升到了 14.2。Archive 的时候一切正常,结果准备上传到 App Store Connect,折腾了半天,卡在一个非常典型的错误上:contains bitcode。一开始我还以为是某个第三方库版本太老,后来排查完才发现,这个坑几乎每个还在维护旧版本 Flutter 项目的同学都会踩到。这篇就把我当时从报错现象到根因、再到完整修复的链路记录下来,给还守着旧工程的人一点参考。

先说结论:旧版本 Flutter 项目在 Xcode 14 之后打包上架报“contain bitcode”,根子基本都出在工程模板默认开启了 Enable Bitcode,再加上一批带有 __LLVM 段的三方库没清理干净。解决方案没有多玄学,但排查顺序和验证手段必须做对,不然你改完可能还是上传失败。

1. 先分清:你遇到的是哪一阶段的 bitcode 报错

很多人在群里甩一条模糊的日志就问“为什么报 contain bitcode”,实际上这个报错分两种完全不同的场景,处理方式也不一样。拿我这次遇到的情况举例,最直观的区分点就是看你在哪一步报的错。

1.1 编译链接期的报错

这种情况发生在 Xcode 的 Build 或者 Archive 阶段,日志里通常长这样:

ld: warning: SomeLibrary.framework/SomeLibrary contains bitcode. You should rebuild it with bitcode disabled.

或者更狠一点,直接用红色报错:

ld: 'Pods/XXX/XXX' does not contain bitcode. You must rebuild it with bitcode enabled.

第一种 warning 其实不影响 Archive,但你会慌;第二种是明确的链接错误,属于 Build Phase 阶段就需要处理。旧 Flutter 项目最常见的是第一种,它不阻断 Archive,但它会直接导致后面上架阶段的一系列连锁反应。

1.2 上传 App Store Connect 阶段的报错

这阶段报错才是真正的“拦路虎”。你用 Xcode 的 Organizer 上传做好的 ipa 时,错误信息一般带 ITMS 编号,最常见的是:

ITMS-90475: Vector. DemoApp.app/DemoApp contains bitcode. The app was submitted with bitcode enabled. Since Xcode 14, bitcode is no longer supported.

如果你更新比较勤快、用 Xcode 15,可能还会看到:

ITMS-90476: The 'DemoApp.app' binary contains bitcode. Since Xcode 14, bitcode is no longer supported. Use Xcode 13 or earlier to submit this app.

对,这就是我这次卡住的点。Archive 成功,本地看起来一切完美,一上传就被 App Store Connect 打回来。凡是遇到 ITMS-90475 或 ITMS-90476,说明你的 App 包内还存在带 bitcode 的切片。注意,不是你主工程写了 ENABLE_BITCODE=NO 就能万事大吉,我从这里开始才意识到问题比想象中深。

下面这张表可以帮你快速对号入座:

报错阶段典型错误信息影响处理优先级
Xcode Build/Archiveld: warning: ... contains bitcodeArchive 能过,但心里没底中
Xcode Build/Archiveld: ... does not contain bitcode直接编译失败高
上传 App Store ConnectITMS-90475 / 90476上传被驳回高

2. bitcode 为什么会在旧版本 Flutter 工程里阴魂不散

要说清楚这个问题,得先讲明白 bitcode 到底是个什么东西。bitcode 是苹果在 LLVM 编译器架构下推行的一种中间代码格式,你可以理解成“源码编译后的半成品”——开发者把这种半成品交给 App Store,苹果在下发到你手机之前,还会根据当时最新的设备架构再做一次最终编译和优化。它曾经是苹果主推的方向,有点“一次提交、多种设备受益”的意思。

但苹果后来很快发现这套机制并没有真正达到预期的效果,反而给开发者、第三方库维护者带来一堆构建麻烦,于是在 Xcode 14 开始直接移除了 bitcode 的支持,新提交的 App 不允许包含 bitcode。

问题是:Xcode 14 移除了支持,但不会自动帮你把老工程里的配置和遗留切片清理掉。

2.1 Flutter 旧版本模板为什么默认开启 bitcode

Flutter 1.x 到 2.0 初期那个年代,新建项目生成的 iOS 工程模板里,ENABLE_BITCODE默认值就是 YES,或者说至少在你的 App Target 和一部分 Pod Target 里是 YES。当年 Xcode 11、12 时代这个设置是能被正常接受的,很多 Flutter 生成的项目就这么一路过来了。

后来 Flutter 官方在新版本模板中把 bitcode 默认关闭了,但关键问题在于:如果你没重新生成工程、也没去改 Build Settings,那个老配置会一直躺在你的 project.pbxproj 里。你换了新 Xcode 打开它,设置项从界面上一看还在,它就会把一个不该存在的默认值带到新的构建流程中。

2.2 你以为关了,其实还有隐藏的 bitcode

这是我最想强调的一点。处理这类报错时,我经常看到有人把自己的主 Target 的 Enable Bitcode 改成 NO 之后重新打包上传,结果还是报同样的错,然后一脸懵。实际上,主 Target 只是入口,你的 Flutter App 里还挂着一堆插件 Framework、CocoaPods 依赖库和 Extension Target。比如:

  • Runner主工程关了,但是NotificationService这类 App Extension 的 Target 可能还开着。
  • 主工程和 Extension 都关了,但某个第三方.framework是动态库,打包时直接原样塞进Frameworks/目录,它内部带__LLVM段,App Store Connect 一扫描照样拒收。

所以,只看主 Target 的配置是典型的思维盲区。我在第一次修复时把所有 Target 全部翻了一遍,才发现有个share_plus对应的 Pod 残留配置还在继承旧的 bitcode 设置。

3. 按日志定位根因:从 DerivedData 到每个 Framework

在动手改配置之前,先静下心把日志环境捋清楚。最忌讳的就是一上来闭着眼把 Enable Bitcode 全部改成 NO,结果连是哪个二进制文件带 bitcode 都不知道。

3.1 第一步:拿到完整原始日志

我处理问题的第一步永远是先把报错日志完整抓下来。xcodebuild 的输出别看那几行红字,要把上下文一起看。比如我用命令构建的时候,会把完整日志导出到文件:

flutter clean flutter pub get cd ios pod install xcodebuild archive \ -workspace Runner.xcworkspace \ -scheme Runner \ -configuration Release \ -archivePath build/Runner.xcarchive \ > build.log 2>&1

然后把build.log里的bitcode关键字全部揪出来:

grep -i bitcode build.log

这样能看到每一个有 bitcode 警告的具体路径。很多时候,一条contains bitcode的 warning 后面会跟着完整的 Framework 路径,这就是第一手嫌疑名单。

3.2 第二步:检查工程里所有 target 的 Enable Bitcode 状态

打开 Xcode,选中 Project,搜索bitcode。你需要看的不只是 Runner 这个 target,每个 target 都要点一遍,包括 App Extension、Today Extension,以及 CocoaPods 的生命周期里出现的 target。

一个非常隐蔽的情况是,Build Settings里搜索不到Enable Bitcode选项,特别是在 Xcode 14+ 的新项目里,默认就没这个设置项。但老项目里它是存在的。你需要确认的是这个列表:

  • Project 级别的ENABLE_BITCODE配置
  • Runner Target 的 Debug / Release / Profile 三种配置
  • 其他 Extension Target 的配置
  • Pods 工程里所有 Pod Target 的配置

如果某个 target 当前值不是 NO,且你还希望保留这个 target,直接改成 NO。但这里有个细节:CocoaPods 的工程通常不是直接改 UI,因为每次pod install都会重新生成 Pods.xcodeproj。所以正确做法是改 Podfile 或者整个 pod 环境,而不是进 Xcode 里逐个去点 Pod 的 Build Settings——你点了也是白点,下次 pod install 就给你冲掉。

3.3 第三步:用 otool 揪出哪些库还藏着 bitcode

这一步是我个人认为最有实用价值的一步。在归档完成之后,你可以直接到.xcarchive里面对 App 包做体检。用otool检查一个二进制文件是否包含 bitcode,核心是看它有没有__LLVM段:

cd build/Runner.xcarchive/Products/Applications/ otool -l Runner.app/Runner | grep -A3 __LLVM

如果看到类似下面的输出:

sectname __bitcode segname __LLVM

说明主二进制还带 bitcode。同理,去检查 Frameworks 目录下所有动态库:

for f in Runner.app/Frameworks/*.framework; do echo "== $f ==" otool -l "$f/$(basename "$f" .framework)" | grep -c __LLVM done

grep -c返回的数字大于 0,就代表这个 framework 里存在 bitcode 段。把步骤 1 日志里的嫌疑库和这里的检查结果对一下,你就能准确锁定目标。

3.4 第四步:确认 CocoaPods 是不是重新 install 过

这一步非常容易忽略。旧 Flutter 项目往往还带着老版本 CocoaPods 生成的 pods 目录,如果只是改了 Build Settings,却没有重新pod install,那 Pods 工程可能还停留在旧状态,连带着各种老配置一起参与构建。

我这次实际上先执行了pod deintegrate,清掉整个 pods 集成关系,然后重新pod install,才保证所有 Pod Target 都基于当前 Build Settings 重新生成。这里的命令行操作在下一章展开,先说结论:如果项目已经很久没动,建议别省这个步骤。

4. 修复操作:关 bitcode、清缓存、重新 Archive 一套流程

确认了根因之后,修复路径就清晰了。下面我按我自己执行的顺序完整过一遍,每一步都标注了作用。

4.1 关闭所有 Target 的 ENABLE_BITCODE

最快的方式是在 Xcode 里逐个 target 搜索bitcode,全部改成 NO。但如果你不想在 UI 上反复点,直接改工程配置文件也行。

找到ios/Runner.xcodeproj/project.pbxproj,搜索ENABLE_BITCODE,你会看到类似这样的片段:

buildSettings = { ... ENABLE_BITCODE = YES; ... };

把整个文件里所有的ENABLE_BITCODE = YES全部替换成:

ENABLE_BITCODE = NO;

我用sed快速处理:

sed -i '' 's/ENABLE_BITCODE = YES/ENABLE_BITCODE = NO/g' ios/Runner.xcodeproj/project.pbxproj

注意这里有个大坑。改完 pbxproj 后千万不要直接打开 Xcode 跑构建,因为 Xcode 在某些情况下会用xcshareddata/xcschemes里的缓存配置覆盖默认设置。我建议改完后顺手 git diff 看一下变动,确认所有 target 都变过来了。

还有一种情况,你的工程里搜索不到ENABLE_BITCODE字段,但依然报 bitcode 相关错误——这代表某个 target 或配置文件用了默认值。此时需要显式添加:在buildSettings里补上ENABLE_BITCODE = NO;即可。

4.2 处理 Podfile 与 pod install 的联动问题

只想改本地不是长久之计,必须让后续任何pod install都默认生成不带 bitcode 的 Pod 工程。最干净的方式是在 Podfile 末尾加一段全局配置:

post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['ENABLE_BITCODE'] = 'NO' end end end

如果项目里用的是 Flutter 的默认 Podfile,通常已经有一小段类似的post_install,只需要把循环里的设置补进去。

改完 Podfile 后执行:

cd ios pod deintegrate pod install

这条组合拳能保证所有 Pod Target 重新生成,并且继承 ENABLE_BITCODE=NO。不要只跑pod install,在旧工程里我还是建议先 deintegrate 再 install,原因很简单:有些遗留配置会被原样保留在 Pods 工程里。

4.3 清理 DerivedData 和 build 缓存

这一步我吃了不少亏。前几次改完设置重新 Archive,居然还在报错,后来排查才发现是 DerivedData 里的缓存没有清干净,Xcode 直接复用了旧的编译产物。所以,在重新构建之前,一定要做一次彻底清理:

rm -rf ~/Library/Developer/Xcode/DerivedData

同时也建议把项目里的 build 目录删掉:

rm -rf build

然后再回到项目根目录:

flutter clean flutter pub get

这套“三连”做完,再重新走一遍 Archive:

cd ios pod install xcodebuild archive \ -workspace Runner.xcworkspace \ -scheme Runner \ -configuration Release \ -archivePath build/Runner.xcarchive

4.4 升级还是原地修:旧版本 Flutter 项目的路线选择

很多人会遇到这样一个抉择:要不要顺便升级 Flutter 版本?我的建议是:如果只是要解决上架报错,先别升级,原地修复是成本最低的路径。

旧 Flutter 项目升级到大版本通常伴随一堆连带问题,比如:

  • 老插件和最新 iOS SDK 不兼容
  • Impeller 渲染引擎变化带来的视觉差异
  • Android 侧 Gradle 配置大换血
  • CocoaPods 版本要求变化

这些问题的排查成本远超 bitcode 本身。原地关掉 bitcode、跑通上架流程,比一次性做版本升级要稳妥得多。等新版本发完,再单独评估升级计划也不迟。

5. 上架阶段再遇 ITMS 相关报错的应对

即使本地 Archive 已经没有任何 bitcode 警告,到了上传阶段依然可能翻车。这一节我再写几种我当时实测过的处理套路。

5.1 ITMS-90475 常见变体和处理

上传报 ITMS-90475 时,错误信息里一般会明确指出是哪个文件包含 bitcode,例如:

ITMS-90475: Vector. Runner.app/Frameworks/SomeFramework.framework/SomeFramework contains bitcode.

这就代表主二进制已经不带 bitcode 了,某个动态 Framework 里还是存在问题。按第 3.3 节的命令去检查对应的 Framework,如果确认它带__LLVM段,处理方案优先排序如下:

  1. 找发行方要最新版本:通常新版 SDK 会去掉 bitcode,这是成本最低的方案。
  2. 确定框架是静态库还是动态库:如果是静态库,链接进主二进制后会被ENABLE_BITCODE=NO的逻辑裁掉,一般不影响上传;如果是动态库,就必须更换为无 bitcode 版本。
  3. 极少数情况自己编译源码:如果这个框架你手里有源码,用当前 Xcode 重新编译一次,build setting 里关掉 bitcode 即可。

5.2 Xcode 14 以后 bitcode 选项消失,怎么应对

切换到 Xcode 14 之后,你会发现有些工程里已经搜不到 Enable Bitcode 了。这不是项目坏了,而是新版 Xcode 的 UI 把 bitcode 相关入口隐藏了。但旧工程里的 pbxproj 可能还保留着ENABLE_BITCODE=YES.

如果你打开 Build Settings 搜索不到,但明确知道自己工程是旧模板,直接按 4.1 节的sed方案处理 pbxproj 最省事。如果工程里确实没有ENABLE_BITCODE字段,且报错来自某个库,就需要先确认这个库是不是用旧 Xcode 编译的,找对应方升级或重编即可。

5.3 用 archive 日志和 altool / Transporter 验证上传

修完之后,最怕的就是“我以为没问题了”。上传验证这块,我习惯的做法是:

先用 Xcode 的 Organizer 选择 Archive,点击 Distribute App,在导出界面选择 App Store Connect,导出 ipa 前会有一步校验;这一步如果能顺利走到导出,基本说明本地归档产物符合预期。如果想完全命令行化,可以用:

xcrun altool --upload-app \ -f Runner.ipa \ -t ios \ -u YOUR_APPLE_ID \ -p YOUR_APP_SPECIFIC_PASSWORD \ --verbose

我自己实际上更推荐用 Transporter 上传,因为它对错误提示更直观。Transporter 能直接把 ITMS 错误展开成可读内容,方便确认最终产物是否还有带 bitcode 的残留库。

6. 修改完成后的验证清单与旧项目维护建议

当整个流程走到这里,我最后再分享一份自己总结的检查清单。以后任何旧 Flutter 项目只要报 bitcode 相关错误,照着这张表走,基本能一遍过。

6.1 先验证产物,再谈上传

每次讲完修复步骤,我都喜欢强调一句:不要光盯着 Xcode 的报错页面看,要验证最终产物里到底还有没有 bitcode。

构建完成后,用这组命令对.app做一次总检查:

# 检查主二进制 otool -l Runner.app/Runner | grep -A3 __LLVM # 检查所有动态库 find Runner.app -name "*.framework" -exec sh -c ' binary_path="$1/$(basename "$1" .framework)" echo "--- $1 ---" otool -l "$binary_path" | grep -q __LLVM && echo "HAS BITCODE" || echo "CLEAN" ' _ {} \; # 或者直接看整个 .app 里的所有可执行文件 find Runner.app -type f -perm +111 -exec otool -l {} \; | grep -B2 -A4 "__LLVM"

如果输出里没有__LLVM,说明本地归档产物已经完全不含 bitcode。我自己是坚持每次 Archive 后都跑一遍这个验证,不然到了上传阶段被拒,又要从头来过。

6.2 检查清单

  • [ ] 所有 Target(包括 Extension)的ENABLE_BITCODE设为 NO
  • [ ] Podfile 的post_install中设置了ENABLE_BITCODE = NO
  • [ ]pod deintegrate+pod install已执行,Pods 工程已重新生成
  • [ ] DerivedData 和 build 目录已清理
  • [ ] Archive 产物通过otool检查确认无__LLVM段
  • [ ] 使用 Xcode Organizer 或 altool/Transporter 走通上传流程
  • [ ] App Store Connect 的构建版本状态显示“正在处理”而非错误

6.3 旧项目维护的最后一点建议

处理完这次问题,我心里最强烈的感受是:老 Flutter 项目就像陈年旧屋,住是能住,但每次碰到新环境都会冒出一两个“装修遗留问题”。bitcode 只是其中一个比较刺眼的。旧项目只要还能稳定发布,就不必过度折腾,但有些基本功这次之后必须养成——

  • 每个依赖升级前,先确认它对 Xcode 14 之后构建链路的兼容性
  • 每次pod install后养成查一下 Build Settings 的习惯
  • 改动 Build Settings 不要只图界面点了就算,务必落到project.pbxproj或 Podfile 这种可持续的配置里

大概就是这样。这次修完之后,同一个包顺利传上去了,审核流程也没再被 bitcode 卡过。如果你现在正被这个报错折磨着,先冷静下来拆解日志,按上面的顺序一步步来,会比直接到处找答案快得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询