问题的由来

在 Apple 发布 M1 芯片之前,一直使用 Intel 的芯片,没有出现什么问题。发布 M1 芯片后,由于两者架构的不同( M1 是 arm64 架构, Intel 是 x86_64 的架构),导致很多软件运行出现了问题。我们在 M1 机型中使用 Xcode 编译模拟器时,可能会碰到如下报错:

ld: in youpath/Pods/UMCommon/UMCommon_7.3.5/UMCommon.framework/UMCommon(UMComBaseEvent.o),

building for iOS Simulator, but linking in object file built for iOS,

file 'youpath/Pods/UMCommon/UMCommon_7.3.5/UMCommon.framework/UMCommon' for architecture arm64

ld: warning: ignoring file YoupPth/Build/Products/Debug-iphonesimulator/FMDB/FMDB.framework/FMDB,

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_$_FMDatabaseQueue", referenced from:

objc-class-ref in SqflitePlugin.o

ld: symbol(s) not found for architecture x86_64

这些报错,都是是由于项目中存在 .a 或 .framework 静态库导致的。以前,我们创建静态库时,会分别打包出一份针对真机(arm64)和模拟器的(x86_64),然后将这两份合并成一个包后引入项目中进行使用。在 Intel 机型上,真机上使用 arm64 指令,模拟器(x86_64)中使用 x86_64 指令,所以不存在问题。但是在 M1 机型上,模拟器是以 arm64 运行的,显然再以 x86_64 运行就会出现问题。

有同学可能会想到包中是有 arm64 指令(真机)的,拿给以 arm64 运行的模拟器使用不就可以了吗?实际上xcode底层并不是这样处理的,它真机就找真机的,模拟器就找模拟器的。

对于这类架构报错问题,网上的资料一般会告诉你两个解决方案:

  • 以Rosetta模式运行Xcode。

  • 修改 Build Settings -> Excluded Architectures 选项,添加Any iOS Simulator SDK选项,并设置值为arm64。图示如下:

  • 这两种方案都能解决编译问题,但是也都存在问题。

    在iOS12及以后,不再支持iphone5及以下机型,而后续的机型都是arm64架构,所以这里不再对之前的armv6/armv7/armv7s/i386 等指令集进行说明。

    Rosetta方案说明

    以 Rosetta 模式运行是 M1 机器上 x86 软件无法运行的解决方案,它会将 x86 指令转译成 ARM 指令运行,这种转译显然是存在性能损耗的,损耗大概在 20%~30% ,不到万不得已,不推荐使用这种方案。

    Excluded Architectures方案说明

    修改 Excluded Architectures 选项也有它的问题。字面意思是排除架构的意思,我们设置在模拟器中排除 arm64 就能解决模拟器无法编译 arm64 的问题。

    这样的设置能生效会让人有点费解,我们知道,在intel机型上,模拟器本来就是以 x86 方式运行的,排除 arm64 毫无影响。但是在 M1 机型上,模拟器是以 arm64 方式运行的,排除了 arm64 反而能跑,这不是把我的智商摁在地上摩擦么?,但是苹果就是这样干的,当在 M1 机型上,排除了模拟器的 arm64 架构后,模拟器还是会以 arm64 的方式运行,但是模拟器中的app是以 x86 的方式运行的,对苹果的这个骚操作我们不得不服。图示如下:

    这种情况下,模拟器和应用会通过 XPC 进行通信,虽然理论上不会有问题,但通信时间会比较长,导致一些依赖计时器判断的逻辑会出问题,例如滑动手势,加速度的判断会出一些问题,导致模拟器里大部分情况下列表无法触发惯性滚动。- by kem

    有时候在 Excluded Architectures 选项中排除了模拟器的 arm64 指令,依然无法编译通过,那么一般是项目设置和cocoapods的设置不一致导致,设置为一致后一般可以解决问题。可以通过在 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"

    通过上述内容,我们知道了问题的由来,它是由于项目中存在 .a 或 .framework ,它们提供的指令集不完整导致的。Apple对于这类问题,也提供了解决方案,请由我细细道来。

    以Xcode13为例,在我们创建静态库时,选择真机编译出来的包只包含 arm64 指令,选择模拟器编译出来的会同时包含 arm64 和 x86_64 指令。我看一些网上的教程,教别人将模拟器部分的 arm64 移除,其实大可不必。因为要支持 M1 机器正常跑模拟器,模拟器必须同时包含 arm64 和 x86_64 指令。

    2019年的 WWDC , apple 提供了一种新的框架封装格式 XCFramework 。简单理解就是以前使用 lipo 合并不同指令集的包,现在则使用新的指令合并成 XCFramework 格式。

    打包成 framework ,格式如下:

    $ tree Release-iphoneos/TestFramework.framework

    Release-iphoneos/TestFramework.framework

    ├── Headers

    │ ├── TestFramework.h

    │ └── TestManager.h

    ├── Info.plist

    ├── Modules

    │ └── module.modulemap

    └── TestFramework

    $ tree Release-iphonesimulator/TestFramework.framework

    Release-iphonesimulator/TestFramework.framework

    ├── Headers

    │ ├── TestFramework.h

    │ └── TestManager.h

    ├── Info.plist

    ├── Modules

    │ └── module.modulemap

    ├── TestFramework

    └── _CodeSignature

    ├── CodeDirectory

    ├── CodeRequirements

    ├── CodeRequirements-1

    ├── CodeResources

    └── CodeSignature

    打包成 XCFramework 后,格式如下:

    $ tree TestFramework.xcframework

    TestFramework.xcframework

    ├── Info.plist

    ├── ios-arm64

    │ └── TestFramework.framework

    │ ├── Headers

    │ │ ├── TestFramework.h

    │ │ └── TestManager.h

    │ ├── Info.plist

    │ ├── Modules

    │ │ └── module.modulemap

    │ └── TestFramework

    └── ios-arm64_x86_64-simulator

    └── TestFramework.framework

    ├── Headers

    │ ├── TestFramework.h

    │ └── TestManager.h

    ├── Info.plist

    ├── Modules

    │ └── module.modulemap

    ├── TestFramework

    └── _CodeSignature

    ├── CodeDirectory

    ├── CodeRequirements

    ├── CodeRequirements-1

    ├── CodeResources

    └── CodeSignature

    从上述可以看出, XCFramework 就是把两个不同指令集的 framework 放入了同一个文件夹( .xcframework ),并生成了一个配置文件 Info.plist 。这样生成的 XCFramework 就可以完美的解决 M1 机器无法编译模拟器的问题。

    XCFramework 的创建指令也很简单:

    # -- 针对.a --

    # 指令:

    xcodebuild -create-xcframework -library <path> [-headers <path>] [-library <path> [-headers <path>]...] -output <path>

    # 样例:

    xcodebuild -create-xcframework -library youpath/TestFramework.a -headers youpath/TestFramework -library youpath/TestFramework.a -headers youpath/TestFramework -output youpath/TestFramework.xcframework

    # -- 针对.framework --

    # 指令:

    xcodebuild -create-xcframework -framework <path> [-framework <path>...] -output <path>

    # 样例:

    xcodebuild -create-xcframework -framework Release-iphoneos/TestFramework.framework -framework Release-iphonesimulator/TestFramework.framework -output TestFramework.xcframework

    解决 M1 机型无法编译模拟器的关键就是针对模拟器的包要同时包含 arm64 和 x86_64 指令集。如果使用只支持 x86_64 指令集的模拟器包,就算打包成 XCFramework 也会依然存在这个问题。

    以现在的情况,很多第三方框架,并没有使用 XCFramework ,而项目中只要有一个框架没有支持模拟器的 arm64 指令,那么在 M1 机器上,模拟器只能以 Rosetta 模式运行应用,对这一块的普遍支持估计要等M1普及以后了。

    参考 资料

  • 苹果换芯,成了开发者们的噩梦?
  • armv6、armv7、armv7s、armv8、armv64及其i386、x86_64区别
  • 细说iOS静态库和动态库
  • 关于Xcode11的XCFrameworks框架
  • 作者:星的天空

    -End-

    最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备! 所有资料都整理到网盘了,欢迎下载!

    面试题 】即可获取

    在看点这里 好文分享给更多人↓↓ 返回搜狐,查看更多

    责任编辑:

    声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。