前言

最近用了一个第三方的Flutter插件,刚开始用项目运行到iOS模拟器就失败了。这个报错在老早以前出现的比较频繁,但是现在还出现属实不应该。

开发环境

  • Flutter: 3.7.11
  • Xcode: 14.3

问题描述

Flutter项目引入第三方插件后,在iOS模拟器运行项目报错:

Building for iOS Simulator, but linking in dylib built for iOS, file 'xxx' for architecture arm64

通过第三方插件ios目录下的xxx.podspec文件可知,这个插件依赖了一个Pod库(已编译的非开源库),插件的作用就是对Pod库进行二次封装。

问题分析

XCFramework出来之前,第三方库(已编译的非开源库)不区分真机和模拟,都是通过lipo命令将真机库和模拟器库合并为一个库,合并后的库中,arm64架构的属于真机,x86_64架构的属于模拟器。

Apple Silicon M1芯片出来前不区分真机和模拟器没什么问题,因为iOS模拟器确实都是x86_64架构的。M系列芯片出来后,Xcode 12支持了arm64架构的iOS模拟器。然后问题来了,在M系列芯片电脑上,Xcode会默认使用arm64架构的iOS模拟器,项目构建时会链接第三方库中arm64架构的二进制文件,又由于arm64架构的二进制文件是属于真机的,如果第三方库是动态库,那么就会出现问题描述中的错误,如果第三方库是静态库,那么出现的错误是这样的:

building for iOS Simulator, but linking in object file built for iOS, file 'xxx' for architecture arm64

这问题已经出来很久了,第三方库的作者早已经做了处理。要么去支持XCFramework,要么在Podspec文件中加上:

# Pod库的构建设置排除arm64架构模拟器
s.pod_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }
# 主工程的构建设置排除arm64架构模拟器
s.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }

按道理来说,现在只要项目依赖的第三方库不是特别老的版本,基本不会遇到这种问题。不过我就是一个例外,同一个Flutter项目,连续在Intel芯片的Mac和M系列芯片的Mac遇到这个问题。

注意,以下分析基于Flutter插件,如果你是纯iOS开发,不了解也没有关系。因为Flutter插件只看iOS相关部分的话,就是一个Pod库,所以以下凡是涉及Flutter插件的都可以看作是Pod库。

1. Intel芯片的Mac上报错

首先对比已经引入的Flutter插件,没发现新引入的有什么问题。接着排查项目中涉及架构的构建设置,还真让我发现了一个问题,Pods工程中的Build Active Architecture Only设置有问题。正常情况下,多环境配置中的Dev环境(属于Debug环境)应该设为Yes,但是实际是No:

screenshot1

那么问题的原因是这个吗?当Build Active Architecture Only设为No时,Xcode构建项目不会局限于当前设备所支持的架构,通常用于Release环境,毕竟打的正式包一般不可能只在当前设备运行;设为Yes时,Xcode构建项目只会基于当前设备所支持的架构。

当前电脑是x86_64架构的,也就是说只支持x86_64架构的iOS模拟器,Build Active Architecture Only设为No意味着Xcode项目构建时将不限于x86_64架构,还会构建arm64架构的。这么一分析,好像确实如此。手动修改设置为Yes,项目重新运行到模拟器,一切正常!

手动修改肯定是不靠谱的,一开始遇到这个问题的时候,没想那么多,直接像这样在Podfile文件中通过Hook匹配构建配置名称将ONLY_ACTIVE_ARCH的值设为YES

post_install do |installer|
  installer.pods_project.build_configurations.each do |config|
    if config.name == 'Dev'
      config.build_settings['ONLY_ACTIVE_ARCH'] = 'YES'
      break
    end
  end
end

后来研究Flutter & iOS问题记录 - 多环境配置下Pod库的宏定义失效问题时,发现完全没必要用Hook,在Podfile文件中给多环境配置映射正确的配置类型才是正常的做法:

project 'app', {
  'Dev' => :debug,
  'Pre' => :release,
  'Prod' => :release,
}

这里有一个很重要的问题当时被我忽略了,在引入这个第三方Flutter插件之前已经引入了不少其他第三方Flutter插件,但是之前都没有报错,所以这个新引入的Flutter插件还是可能有问题的。如果后面没有再次遇到这个问题,我还真以为这个问题已经被彻底解决。

2. M系列芯片的Mac上报错

为了方便调试和分析,新建一个Flutter项目,同时参考报错的第三方Flutter插件也新建一个,就叫test_plugin。运行测试项目到iOS模拟器,问题成功复现,找到构建日志:

screenshot2

咦,怪不得报错,竟然会针对arm64架构进行编译链接。针对arm64架构编译不报错是因为这是源码编译,无所谓什么架构,链接报错是因为被链接的库(Flutter插件依赖的第三方库)不支持arm64架构的iOS模拟器。

找到test_plugin的构建设置:

screenshot3

模拟器竟然没有排除arm64架构,这是怎么回事?打开test_plugin.podspec文件发现了问题:

s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }

VALID_ARCHS设置已经过时失效,现在应该用EXCLUDED_ARCHS设置。关于这些构建设置的使用可以参考文档:TN3117: Resolving architecture build errors on Apple silicon

screenshot4

看来这报错的Flutter插件的创建时间有点久远了,从Flutter框架项目的提交记录看,在2020年Podspec文件模板已经开始改用EXCLUDED_ARCHS设置:

screenshot5

不过,改用新模板的设置能解决问题吗?看这也没排除arm64架构。

s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }

改为这个实测不行。在设置中的i386的后面加上arm64

s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386 arm64' }

再次运行,弹出了这个:

screenshot6

点击Build for Rosetta确实也能正常运行,如果想切换回原来的目标设备列表,按这个步骤[Xcode菜单栏] -> [Product] -> [Destination] -> [Destination Architectures] -> [Show Apple Silicon Destinations]操作。

虽然能运行,但是不想用Rosetta怎么办?很好解决,在主工程的构建设置中设置排除arm64架构的模拟器。有两种设置方式:

  1. 手动设置

screenshot7

  1. 通过Flutter插件的Podspec文件设置
s.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }

注意,低版本的iOS模拟器只支持Rosetta,至于具体到多低的版本,推测是低于iOS 14的版本,因为iOS 14/Xcode 12/M1芯片都是2020年发布的。

可以通过file命令查看iOS模拟器支持的架构,例如查看Xcode 14.3内置的iOS模拟器:

file /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator

执行输出:

/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64]
/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator (for architecture x86_64):	Mach-O 64-bit executable x86_64
/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator (for architecture arm64):	Mach-O 64-bit executable arm64

从输出内容可以看到, iOS 16.4模拟器同时支持x86_64和arm64架构。如果是 iOS 12.4模拟器,输出是这样的:

/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/MacOS/iOS 12.4: Mach-O 64-bit bundle x86_64

iOS 12.4模拟器只支持x86_64架构,如果只显示Apple Silicon Destinations列表会出现找不到iOS 12.4模拟器的情况。建议按这个步骤[Xcode菜单栏] -> [Product] -> [Destination] -> [Destination Architectures] -> [Show Both]操作,显示全部iOS模拟器。

问题分析到这还没完,为什么其他还用着VALID_ARCHS设置的Flutter插件没报错呢?

3. 动态库引起的报错

对比多个Flutter插件的构建日志,我发现了报错的另一个原因,竟然是因为动态库。

Flutter插件报错时的构建日志:

screenshot2

test_plugin.podspec文件中加上这行代码指定为静态库:

s.static_framework = true

重新构建的日志:

screenshot8

一切正常!构建静态库时只编译不链接,避免了链接报错。等到构建APP时,由于主工程的构建设置中已经排除了arm64架构,所以只会基于x86_64架构构建,这时链接也不会报错。

虽然可以通过将动态库修改为静态库这种方式来解决问题,但这个问题本质还是架构相关的问题,所以还是围绕架构来解决。

解决方案

这里提供三个解决方法,请根据需要任选一个即可:

  1. iOS模拟器排除arm64架构(设置Pods项目的构建设置)

Podfile文件中加上:

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

这个方法只适用于解决因为使用过时VALID_ARCHS设置导致的报错。如果本身使用的是EXCLUDED_ARCHS设置,又没有加上$(inherited),会导致这个方法失效。例如Podspec文件这样设置:

s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }

会因为优先级问题,无法生效:

screenshot9

  1. iOS模拟器排除arm64架构(设置Target的构建设置)
  • iOS项目在Podfile文件中加上:
post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      excluded_archs = config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]']
      excluded_archs = excluded_archs.nil? ? '' : excluded_archs
      if !excluded_archs.include?('arm64')
        excluded_archs = "#{excluded_archs} arm64"
      end
      config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = excluded_archs
    end
  end
end
  • Flutter项目在Podfile文件中加上:
post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    target.build_configurations.each do |config|
      excluded_archs = config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]']
      excluded_archs = excluded_archs.nil? ? '' : excluded_archs
      if !excluded_archs.include?('arm64')
        excluded_archs = "#{excluded_archs} arm64"
      end
      config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = excluded_archs
    end
  end
end

根据EXCLUDED_ARCHS设置的值决定要不要追加排除arm64架构,如果值中已经有了arm64便不做改动。由于是设置Target的构建设置,优先级高,所以不会出现第一个方法中的问题。注意,尽量不要整个直接复制粘贴,请根据项目的实际情况修改。如有疑问,欢迎留言评论。

  1. 设置需要构建的架构(终极方法

Podfile文件中加上:

post_install do |installer|
  installer.pods_project.build_configurations.each do |config|
    config.build_settings['ARCHS[sdk=iphonesimulator*]'] = 'x86_64'
  end
end

这段代码的作用是设置iOS模拟器只构建x86_64架构,专治各种关于架构的问题,你值得拥有。

注意:修改Podfile文件后,需要重新执行pod install命令让改动生效。

2023/08/14更新:

如果以上三个解决方法都没能解决你的问题,请检查主工程的构建设置中是否设置了排除arm64架构的模拟器:

screenshot7

如果没有设置,手动设置即可。

补充内容1

你可能还会遇到以下问题:

  • Undefined symbol: _OBJC_CLASS_$_xxx

出现这个问题的原因有两种。一是被链接的库(开源库)没有排除arm64架构,默认只编译了arm64架构,而第三方库排除了arm64架构,只编译x86_64架构;二是被链接的库(开源库)排除了arm64架构,只编译x86_64架构,但是第三方库没排除arm64架构。这叫什么?这叫互相错过,爱而不得。

  • Unsupported Swift architecture

根据构建日志,可以找到报错的文件xxx-Swift.h,内容是这样的:

#if 0
#elif defined(__arm64__) && __arm64__
...
#else
#error unsupported Swift architecture
#endif

看到__arm64__,基本可以确定也是架构的问题。找到报错库的Podspec文件修改为排除arm64架构,重新构建不再报错,文件xxx-Swift.h的内容变为这样:

#if 0
#elif defined(__x86_64__) && __x86_64__
...
#else
#error unsupported Swift architecture
#endif

以上问题都是关于架构的同一类问题,具体解决方法请看前面。

补充内容2

从前面的问题分析可知,现在的iOS模拟器同时支持x86_64和arm64架构,那么该如何判断当前运行的模拟器是哪个架构的呢?可以通过打印设备型号判断:

  • Objective-C
#import <sys/utsname.h>
struct utsname systemInfo;
uname(&systemInfo);
NSLog(@"machine: %@", @(systemInfo.machine));
  • Swift
var systemInfo = utsname()
uname(&systemInfo)
let machine = withUnsafePointer(to: &systemInfo.machine.0) { ptr in
    return String(cString: ptr)
}
print("machine: \(machine)")

如果打印结果为machine: x86_64,说明当前运行的是x86_64架构的iOS模拟器;如果打印结果为machine: arm64,说明是arm64架构的iOS模拟器。

补充内容3

如果运行项目时遇到以下问题:

/xxx/Pods-xxx-frameworks.sh: line 132: ARCHS[@]: unbound variable

可能是因为在[Build Settings] -> [Architectures] -> [Excluded Architectures]设置中将目标设备支持的架构排除了,例如运行项目到真机,但是排除了arm64架构,修改设置可以解决该问题。

最后

如果这篇文章对你有所帮助,点赞👍收藏🌟支持一下吧,谢谢~


本篇文章由@crasowas发布于CSDN。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐