iOS M1 芯片 编译爬坑

主要是两个错误,引起混淆。导致爬了挺久的坑。

1、 In xxxx/proj.ios_mac/xxxx.framework/xxxx(xxxx.a-arm64-master.o), building for iOS Simulator, but linking in object file built for iOS, for architecture arm64.
或者
ld: warning: ignoring file YoupPth/Build/Products/Debug-iphonesimulator/xxxx/xxxx.framework/xxxx, 
building for iOS Simulator-x86_64 but attempting to link with file built for iOS Simulator-arm64
Undefined symbols for architecture x86_64:
  "_OBJC_CLASS_$_xxxx", referenced from:
      objc-class-ref in xxxx.o
ld: symbol(s) not found for architecture x86_64
    
    
2、 In xxxx/proj.ios_mac/xxxx.xcodeproj The linked framework 'Pods_xxx_iOS.framework' is missing one or more architectures required by this target: x86_64.

看起来好像是差不多,都是由于 项目中的依赖库 提供的指令集 不完整导致的。

第一个 是说  xxxx.framework  指令集为 iOS 真机 这个库, 不能 链接 为 iOS 模拟器 的 目标文件。 实际上这个问题 应该是一个兼容问题,只出现在 M1 芯片的 Mac 上。 因为 M1 芯片的 Mac 本身就是 arm64 架构,它的模拟器也是 arm64 架构的。

首先有一点要明确:

我们在 intel 机型上 创建一个静态库文件,编译后会生成两个版本。一个是模拟器版, x86_64 架构(如果M1 机型上创建 那么是 arm64 架构),一个是真机版, arm64 架构。

如果 intel 机型上 生成的静态库 需要 给 intel 机型上的 其他工程引入,一般操作是通过 lipo 工具合并成一个 模拟器和真机 通用的 静态库,以方便模拟器或者真机调试。

链接真机的时候 会把真机的指令 链接进 目标文件,链接模拟器 就把模拟器的指令链接进 目标文件

当链接的时候: 针对 一般第三方库, 以前普遍提供的是 包含真机 arm64 指令集 和 模拟器 x86_64 指令集的 通用版本。

在 intel 机型上也就是你的Mac 打包机器为 intel 芯片,  为真机链接 arm64 架构 指令,为模拟器链接 x86_64 架构 指令没有什么问题。

但是 你的Mac 打包机器为 M1 芯片的时候,   为真机链接 arm64 架构指令,为模拟器 还是链接 x86_64 指令就会有问题。因为 此时 M1 机型 的模拟器架构是 arm64! 只能跑 arm64 指令。

这时候 Xcode 就会报以上类型的错误,而不会去为 模拟器 链接 该依赖库 真机的 arm64 指令!!!

验证很简单:

你首先针对 真机 生成一个静态库,在M1 机型上 链接到 模拟器的可执行文件,依然不行。即使他们 都是在 arm64 架构下。

 

那么如何解决这个问题? 有以下两种方式。

1、使用 Rosetta 模式 运行Xcode, 重新编译。

2005年, 苹果从PowerPC 芯片切换到 因特尔,Rosetta 最初是为 PowerPC 应用转换到 x86 上,能让大多数 PowerPC 应用在 x86 Mac 上运行,但是会损失部分性能。 

Rosetta 模式 会使 Xcode 能够 在链接 x86静态库 前 将其转换为 arm64 指令。下图 倒数第二行 使用 Rosetta 打开! (注意 只有 M1 机型上才有)

通过 Rosetta 模式运行 Xcode, Xcode 是以 x86架构的方式运行(正常是 Apple 也就是 arm64), 静态库是 x86 的也能正常链接跑。  

在 M1 的 Mac 机型,模拟器 正常是以 arm64 运行的,模拟器 启动 app 也是以 arm64 的方式运行。

而PC 上 Rosetta 模式启动 Xcode 让 App 链接依赖库, 链接器会链接 x86(也就是当前Xcode 运行模式)的指令,即使依赖库既有 arm64 也有 x86 指令集。如果 依赖库本身只有 arm64 指令集,那么会报 未定义 符号,无法链接。

注意:Rosetta 的方式 虽然 足以支持大多数主流生产力应用程序,但无法兼容那些需要与操作系统、硬件或图形硬件进行直接交互的软件。

 

2、编译链接 app 工程的时候  排除 arm64 架构。 具体 在 Build Settings 里 设置 Excluded Architectures 选项,在 Debug 模式下 添加 arm64 。真机不会出现这个问题,所以 Release 不需要。

所以先 检查工程以及子工程 编译设置 排除 arm64 架构的编译产物。 

检查 Build Settings 里面的 Excluded Architectures 添加  arm64。  clean 重编。 

最终跑起来  会发现  排除了 arm64 指令集后 模拟器也是以 x86 的方式启动 App 了。 

查看 活动监视器 里面  进程的 种类 就可以清晰的知道。 

模拟器与 模拟器启动的 App 运行指令集不同,通过 XPC 方式进行通信,理论上没有啥问题,但是 XPC 通信 相对同步时间比较长, 对于 时间敏感的逻辑可能会出问题。

比如 滑动手势 或者 列表的惯性滚动。 

 

对于通过 cocoapods 维护依赖库的方式来说, 如果手动更改后,重新  pod update 会导致又要重新修改,所以excluded Architectures  需要写进 Podfile

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = "arm64"
    end
  end
end

 

但是如果需要 编译依赖库文件 提供给第三方 使用 那么你的生成产物又不能排除 arm64.  你可能就需要针对不同 的架构打不同的版本。有更好的解决办法。

为了解决 M1 芯片机型 封装库文件的问题, 2019年 的 WWDC 大会  Apple 提供了 XCFrameworks 的方式,这就可以理解成 新的 xcodebuild 命令替换以前  lipo 命令,将不同指令集 的库文件合并成 通用二进制文件。

只不过 XCFrameworks 还可以包含其他第三方的库。 这里就不展开说 XCFrameworks 了。

 

文章开头说的第二个错误 是说 链接 Pods_xxxx_iOS.framework 这个framework 中 缺失了 x86_64 架构 的某个库。

解决:检查 编译 生成 Pods_xxxx_iOS.framework 的 所有依赖库的工程 或者 prebuilt 的库,是否存在 x86_64 架构。

1、针对子工程,如果 Xcode 13, 检查工程的 User-Defined 里面的  VALID_ARCHS 值 是否存在  x86_64,没有则需要添加。 如果是 Xcode 12 则在  Architectures 里面检查 是否存在 x86_64。其他架构也是同样

2、针对 预编译库, 可以使用 lipo 工具查看包含一些什么 指令集。是否缺失 x86_64。如果缺失了,需要获取带有 x86_64 指令的依赖库。

例如:lipo -i xxx.a

主要是 Xcode12 默认不再对 x86_64 架构 进行支持了,打通用包的时候  需要手动添加。

 

lipo 工具 可以对  库文件的架构进行修改

nm 工具 用来现实一个 程序包的符号表

strip 用来删除一个 程序包里的符号表

ar 工具 可以获取 库文件 链接前的  .o 文件  

libtool -static -o  xxx.a  *.o   又可以合并 *.o 文件 为 xxx.a。 或者 直接用 ld。

 

参考:

https://juejin.cn/post/7037037120158269448

posted @ 2022-05-20 21:05  lesten  阅读(4659)  评论(0编辑  收藏  举报