M1 設(shè)備的 Xcode 編譯問(wèn)題深究
作者:星的天空
https://juejin.cn/post/7037037120158269448
問(wèn)題的由來(lái)
在Apple發(fā)布M1芯片之前,一直使用Intel的芯片,沒(méi)有出現(xiàn)什么問(wèn)題。發(fā)布M1芯片后,由于兩者架構(gòu)的不同(M1是arm64架構(gòu),Intel是x86_64的架構(gòu)),導(dǎo)致很多軟件運(yùn)行出現(xiàn)了問(wèn)題。我們?cè)?code style="font-size: 14px;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">M1機(jī)型中使用Xcode編譯模擬器時(shí),可能會(huì)碰到如下報(bào)錯(cuò):
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
這些報(bào)錯(cuò),都是是由于項(xiàng)目中存在.a或.framework靜態(tài)庫(kù)導(dǎo)致的。以前,我們創(chuàng)建靜態(tài)庫(kù)時(shí),會(huì)分別打包出一份針對(duì)真機(jī)(arm64)和模擬器的(x86_64),然后將這兩份合并成一個(gè)包后引入項(xiàng)目中進(jìn)行使用。在Intel機(jī)型上,真機(jī)上使用arm64指令,模擬器(x86_64)中使用x86_64指令,所以不存在問(wèn)題。但是在M1機(jī)型上,模擬器是以arm64運(yùn)行的,顯然再以x86_64運(yùn)行就會(huì)出現(xiàn)問(wèn)題。
有同學(xué)可能會(huì)想到包中是有
arm64指令(真機(jī))的,拿給以arm64運(yùn)行的模擬器使用不就可以了嗎?實(shí)際上x(chóng)code底層并不是這樣處理的,它真機(jī)就找真機(jī)的,模擬器就找模擬器的。
解決方案
常用方案
對(duì)于這類(lèi)架構(gòu)報(bào)錯(cuò)問(wèn)題,網(wǎng)上的資料一般會(huì)告訴你兩個(gè)解決方案:
-
以Rosetta模式運(yùn)行Xcode。 -
修改 Build Settings->Excluded Architectures選項(xiàng),添加Any iOS Simulator SDK選項(xiàng),并設(shè)置值為arm64。圖示如下:
這兩種方案都能解決編譯問(wèn)題,但是也都存在問(wèn)題。
在iOS12及以后,不再支持iphone5及以下機(jī)型,而后續(xù)的機(jī)型都是arm64架構(gòu),所以這里不再對(duì)之前的armv6/armv7/armv7s/i386 等指令集進(jìn)行說(shuō)明。
Rosetta方案說(shuō)明
以Rosetta模式運(yùn)行是M1機(jī)器上x86軟件無(wú)法運(yùn)行的解決方案,它會(huì)將x86指令轉(zhuǎn)譯成ARM指令運(yùn)行,這種轉(zhuǎn)譯顯然是存在性能損耗的,損耗大概在20%~30%,不到萬(wàn)不得已,不推薦使用這種方案。
Excluded Architectures方案說(shuō)明
修改Excluded Architectures選項(xiàng)也有它的問(wèn)題。字面意思是排除架構(gòu)的意思,我們?cè)O(shè)置在模擬器中排除arm64就能解決模擬器無(wú)法編譯arm64的問(wèn)題。
這樣的設(shè)置能生效會(huì)讓人有點(diǎn)費(fèi)解,我們知道,在intel機(jī)型上,模擬器本來(lái)就是以x86方式運(yùn)行的,排除arm64毫無(wú)影響。但是在M1機(jī)型上,模擬器是以arm64方式運(yùn)行的,排除了arm64反而能跑,這不是把我的智商摁在地上摩擦么?,但是蘋(píng)果就是這樣干的,當(dāng)在M1機(jī)型上,排除了模擬器的arm64架構(gòu)后,模擬器還是會(huì)以arm64的方式運(yùn)行,但是模擬器中的app是以x86的方式運(yùn)行的,對(duì)蘋(píng)果的這個(gè)騷操作我們不得不服。圖示如下:

這種情況下,模擬器和應(yīng)用會(huì)通過(guò)
XPC進(jìn)行通信,雖然理論上不會(huì)有問(wèn)題,但通信時(shí)間會(huì)比較長(zhǎng),導(dǎo)致一些依賴(lài)計(jì)時(shí)器判斷的邏輯會(huì)出問(wèn)題,例如滑動(dòng)手勢(shì),加速度的判斷會(huì)出一些問(wèn)題,導(dǎo)致模擬器里大部分情況下列表無(wú)法觸發(fā)慣性滾動(dòng)。- by kem
其它問(wèn)題
有時(shí)候在Excluded Architectures選項(xiàng)中排除了模擬器的arm64指令,依然無(wú)法編譯通過(guò),那么一般是項(xiàng)目設(shè)置和cocoapods的設(shè)置不一致導(dǎo)致,設(shè)置為一致后一般可以解決問(wèn)題??梢酝ㄟ^(guò)在Podfile中添加如下內(nèi)容來(lái)解決:
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
最優(yōu)解
通過(guò)上述內(nèi)容,我們知道了問(wèn)題的由來(lái),它是由于項(xiàng)目中存在.a或.framework,它們提供的指令集不完整導(dǎo)致的。Apple對(duì)于這類(lèi)問(wèn)題,也提供了解決方案,請(qǐng)由我細(xì)細(xì)道來(lái)。
以Xcode13為例,在我們創(chuàng)建靜態(tài)庫(kù)時(shí),選擇真機(jī)編譯出來(lái)的包只包含arm64指令,選擇模擬器編譯出來(lái)的會(huì)同時(shí)包含arm64和x86_64指令。我看一些網(wǎng)上的教程,教別人將模擬器部分的arm64移除,其實(shí)大可不必。因?yàn)橐С?code style="font-size: 14px;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">M1機(jī)器正常跑模擬器,模擬器必須同時(shí)包含arm64和x86_64指令。
2019年的WWDC,apple提供了一種新的框架封裝格式XCFramework。簡(jiǎn)單理解就是以前使用lipo合并不同指令集的包,現(xiàn)在則使用新的指令合并成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就是把兩個(gè)不同指令集的framework放入了同一個(gè)文件夾(.xcframework),并生成了一個(gè)配置文件Info.plist。這樣生成的XCFramework就可以完美的解決M1機(jī)器無(wú)法編譯模擬器的問(wèn)題。
XCFramework的創(chuàng)建指令也很簡(jiǎn)單:
# -- 針對(duì).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
# -- 針對(duì).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機(jī)型無(wú)法編譯模擬器的關(guān)鍵就是針對(duì)模擬器的包要同時(shí)包含arm64和x86_64指令集。如果使用只支持x86_64指令集的模擬器包,就算打包成XCFramework也會(huì)依然存在這個(gè)問(wèn)題。
后記
以現(xiàn)在的情況,很多第三方框架,并沒(méi)有使用XCFramework,而項(xiàng)目中只要有一個(gè)框架沒(méi)有支持模擬器的arm64指令,那么在M1機(jī)器上,模擬器只能以Rosetta模式運(yùn)行應(yīng)用,對(duì)這一塊的普遍支持估計(jì)要等M1普及以后了。
參考資料
蘋(píng)果換芯,成了開(kāi)發(fā)者們的噩夢(mèng)?
armv6、armv7、armv7s、armv8、armv64及其i386、x86_64區(qū)別
細(xì)說(shuō)iOS靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)
關(guān)于Xcode11的XCFrameworks框架
作者:星的天空
https://juejin.cn/post/7037037120158269448
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來(lái),可以說(shuō)是程序員面試必備!所有資料都整理到網(wǎng)盤(pán)了,歡迎下載!

面試題】即可獲取
