项目刚搭好空工程骨架,一行业务代码都还没写。先跑一次模拟器编译,确认工程配置没问题。
编译失败。
java
Building for 'iOS-simulator', but linking in object file
(.../MLImage.framework/MLImage[arm64]...) built for 'iOS'
这不是那种"缺一个 import"或者"类型写错了"的编译错误。这是链接器在说:"你要给模拟器编译,但这个 framework 里的 arm64 部分是给真机用的。"
MLImage 是一个机器学习相关的 framework,通过 CocoaPods 引入。但问题是它为什么会在模拟器构建时把真机版本链接进来?
第一反应:检查架构排除配置
CocoaPods 的老项目通常会有一堆架构排除配置,用来在模拟器构建时跳过不适用的架构。打开 Pods 目录下的 xcconfig 文件,确实看到了:
css
EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64
这行配置的意图很清楚:模拟器构建时排除 arm64,只留 x86_64。看起来是对的。那为什么还是报"arm64 是给 iOS 的"?
试着反过来:如果在模拟器构建时保留了 arm64 呢?去掉 EXCLUDED_ARCHS,让它自然链接。结果一样------因为 MLImage 里的 arm64 slice 确实是 device 版本,保留它链接器就会报这个错。
绕了一圈回到原点:不是排除策略的问题,是 MLImage 这个 framework 本身没有提供 arm64-simulator 的 slice。
第二反应:确认 framework 里到底有什么
用 lipo -info 看一下:
ruby
$ lipo -info MLImage.framework/MLImage
Architectures in the fat file: MLImage are: x86_64 arm64
两个架构都有。x86_64 和 arm64。但如果两个架构都是对的,为什么链接器说 arm64 是 device 的?
lipo -info 只能告诉你"有哪些 CPU 架构",不能告诉你"每个架构是给哪个平台编译的"。Apple Silicon Mac 出现之后,arm64 这个架构同时存在于两个平台------iOS device 和 iOS Simulator。单看 lipo 输出,你不知道一个 arm64 slice 到底是给真机的还是给模拟器的。
进一步查一下 Mach-O 的 LC_BUILD_VERSION:
markdown
$ vtool -show-build MLImage.framework/MLImage
MLImage.framework/MLImage (architecture arm64):
platform 2 (iOS)
minos 15.0
MLImage.framework/MLImage (architecture x86_64):
platform 7 (iOS Simulator)
minos 15.0
platform 2 是 iOS device。platform 7 是 iOS Simulator。
这就是问题的根因:MLImage 的 arm64 slice 是为真机编译的,不是为模拟器。在 Intel Mac 时代这不会出问题------模拟器只跑 x86_64。但 Apple Silicon Mac 上,模拟器原生跑 arm64,链接器会优先选择 arm64 slice------然后发现它是 device 的,报错。
python
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 排查路径:从报错到根因 │
│ │
│ 报错:arm64 是给 iOS 的,不能用于 simulator │
│ │ │
│ ├── 第一反应:检查 EXCLUDED_ARCHS │
│ │ → 配置是对的,排除策略本身没毛病 │
│ │ │
│ ├── 第二反应:用 lipo 看 framework 有哪些架构 │
│ │ → x86_64 + arm64,两个架构都有 │
│ │ → 但 lipo 只能告诉你"有哪些 CPU" │
│ │ → 不能告诉你"arm64 是 device 还是 simulator" │
│ │ │
│ └── 第三反应:用 vtool 查 Mach-O 的 LC_BUILD_VERSION │
│ → arm64 slice: platform 2 (iOS) │
│ → x86_64 slice: platform 7 (iOS Simulator) │
│ → 确认:MLImage 没有 arm64-simulator slice │
│ │
│ 根因:老版本 MLKit 的 MLImage framework 不包含 arm64-simulator │
│ 二进制。Apple Silicon Mac 出来后这个缺口暴露了。 │
│ │
└─────────────────────────────────────────────────────────────────┘
为什么之前没出过这个问题
在 Intel Mac 上,模拟器运行的是 x86_64 指令集。MLImage 有 x86_64 的 simulator slice,所以链接器选它,没问题。
Apple Silicon Mac 出现后,模拟器可以原生跑 arm64。链接器在 arm64 simulator 构建时会优先选 arm64 slice------但 MLImage 的 arm64 是 device 的。于是就报错了。
这跟 CocoaPods 的 EXCLUDED_ARCHS 配置没有直接关系。即使你设置 EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64,链接器在 simulator SDK 下跳过 arm64、用 x86_64------可以编译,但你失去的是 Apple Silicon 模拟器的原生性能。每次跑模拟器都在走 Rosetta 转译。
三种处理方式,只有一种是对的
不推荐:手动改 Pods 目录下的二进制。
有人可能会想:既然 arm64 是 device 的,把它从 fat binary 里抽掉,只留 x86_64?或者把 device 的 arm64 用某种方式签成 simulator 的?
这两种做法都不成立。第一种只是把问题藏起来,下一次 pod install 全部覆盖。第二种根本没有可行性------device 和 simulator 的 arm64 虽然指令集相同,但链接的目标库和系统调用完全不同,不可能通过改签名的方式互换。
临时绕行:用 x86_64 跑 Rosetta。
在 EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64 的基础上,确保整个构建链都走 x86_64。这能让你先跑起来,但模拟器性能会明显下降------不是原生 arm64 速度。
这个方案只能用于短期诊断,不能作为长期配置。
正确的长期方案:升级依赖。
问题的本质是 MLImage 没有提供 arm64-simulator slice。这在新版本的 Google MLKit 里已经修复了------新版本以 XCFramework 格式发布,XCFramework 里可以为每个 platform + architecture 组合提供独立的二进制。
升级 MLKit 到包含 arm64-simulator slice 的版本,重新 pod install,确认 Pods/MLImage 下的二进制同时包含 platform 2 (iOS) arm64 和 platform 7 (iOS Simulator) arm64。之后模拟器构建就可以直连 arm64,既有原生性能,又不会报错。
查了这么多,最后改的代码是零行
整个排查过程花了不少时间------从编译报错出发,走了 EXCLUDED_ARCHS 的弯路,用 lipo 排除了表面怀疑,最后用 Mach-O 的 platform 字段确认了根因。
但最终,一行代码都没改。修改的是 Podfile 里一个 SDK 的版本号,然后重新执行 pod install。
这类问题的特点就是:排查链很长,修复动作很小。 但如果不把排查链走到根因,很容易停在"EXCLUDED_ARCHS 不对"这个错误结论上,然后反复折腾架构排除配置,浪费更多时间。
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 三种方案 │
│ │
│ ❌ 手动改 Pods/ 下的二进制 │
│ → 下次 pod install 全丢 │
│ → 不能把 device arm64 签成 simulator arm64 │
│ │
│ ⚠️ 用 x86_64 + Rosetta 临时绕行 │
│ → 能编译,但模拟器性能下降 │
│ → 只能短期用,不适合长期配置 │
│ │
│ ✅ 升级到包含 arm64-simulator 的版本 │
│ → 根因修复 │
│ → Apple Silicon 模拟器原生性能 │
│ → 不需要 ARCHS 排除配置 │
│ │
└─────────────────────────────────────────────────────────────────┘
排查这类问题的通用方法
以后遇到"模拟器能编译但在 arm64 上链接报错"的问题,可以直接走这个流程:
- 用
lipo -info看 framework 有哪些架构 - 如果 arm64 存在,用
vtool -show-build查它的LC_BUILD_VERSION - 如果 platform 不是 simulator(platform 7),说明这个 framework 没有 arm64-simulator slice
- 查这个依赖有没有新版本支持 XCFramework 或 arm64-simulator
- 如果是自己编译的 framework,重新用正确的 destination 编译一次
一套下来的关键是:不要停在 lipo 的输出上。 看到 arm64 就以为它支持模拟器,是这类排查里最常见的误判。
依赖排查的起点往往是一行编译错误,终点往往只改一行配置。中间那段路,走得快不快,取决于有没有先确认"这个 framework 的 arm64 到底是给谁用的"。