最近用了一个第三方的Flutter插件,刚开始用项目运行到iOS模拟器就失败了。这个报错在老早以前出现的比较频繁,但是现在还出现属实不应该。
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库。
首先对比已经引入的Flutter插件,没发现新引入的有什么问题。接着排查项目中涉及架构的构建设置,还真让我发现了一个问题,Pods工程中的Build Active Architecture Only
设置有问题。正常情况下,多环境配置中的Dev环境(属于Debug环境)应该设为Yes
,但是实际是No
:
那么问题的原因是这个吗?当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插件还是可能有问题的。如果后面没有再次遇到这个问题,我还真以为这个问题已经被彻底解决。
为了方便调试和分析,新建一个Flutter项目,同时参考报错的第三方Flutter插件也新建一个,就叫test_plugin
。运行测试项目到iOS模拟器,问题成功复现,找到构建日志:
咦,怪不得报错,竟然会针对arm64架构进行编译链接。针对arm64架构编译不报错是因为这是源码编译,无所谓什么架构,链接报错是因为被链接的库(Flutter插件依赖的第三方库)不支持arm64架构的iOS模拟器。
找到test_plugin
的构建设置:
模拟器竟然没有排除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。
看来这报错的Flutter插件的创建时间有点久远了,从Flutter框架项目的提交记录看,在2020年Podspec文件模板已经开始改用EXCLUDED_ARCHS
设置:
不过,改用新模板的设置能解决问题吗?看这也没排除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' }
再次运行,弹出了这个:
点击Build for Rosetta
确实也能正常运行,如果想切换回原来的目标设备列表,按这个步骤[Xcode菜单栏] -> [Product] -> [Destination] -> [Destination Architectures] -> [Show Apple Silicon Destinations]操作。
虽然能运行,但是不想用Rosetta
怎么办?很好解决,在主工程的构建设置中设置排除arm64架构的模拟器。有两种设置方式:
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插件没报错呢?
对比多个Flutter插件的构建日志,我发现了报错的另一个原因,竟然是因为动态库。
Flutter插件报错时的构建日志:
在test_plugin.podspec
文件中加上这行代码指定为静态库:
s.static_framework = true
重新构建的日志:
一切正常!构建静态库时只编译不链接,避免了链接报错。等到构建APP时,由于主工程的构建设置中已经排除了arm64架构,所以只会基于x86_64架构构建,这时链接也不会报错。
虽然可以通过将动态库修改为静态库这种方式来解决问题,但这个问题本质还是架构相关的问题,所以还是围绕架构来解决。
这里提供三个解决方法,请根据需要任选一个即可:
在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' }
会因为优先级问题,无法生效:
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
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的构建设置,优先级高,所以不会出现第一个方法中的问题。注意,尽量不要整个直接复制粘贴,请根据项目的实际情况修改。如有疑问,欢迎留言评论。
在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架构的模拟器:
如果没有设置,手动设置即可。
你可能还会遇到以下问题:
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
以上问题都是关于架构的同一类问题,具体解决方法请看前面。
从前面的问题分析可知,现在的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模拟器。
如果运行项目时遇到以下问题:
/xxx/Pods-xxx-frameworks.sh: line 132: ARCHS[@]: unbound variable
可能是因为在[Build Settings] -> [Architectures] -> [Excluded Architectures]设置中将目标设备支持的架构排除了,例如运行项目到真机,但是排除了arm64架构,修改设置可以解决该问题。
如果这篇文章对你有所帮助,点赞收藏支持一下吧,谢谢~
本篇文章由@crasowas发布于CSDN。