問題的由來
在Apple發(fā)布M1芯片之前,一直使用Intel的芯片,沒有出現(xiàn)什么問題。發(fā)布M1芯片后,由于兩者架構(gòu)的不同(M1是arm64架構(gòu),Intel是x86_64的架構(gòu)),導(dǎo)致很多軟件運行出現(xiàn)了問題。我們在M1機(jī)型中使用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靜態(tài)庫導(dǎo)致的。以前,我們創(chuàng)建靜態(tài)庫時,會分別打包出一份針對真機(jī)(arm64)和模擬器的(x86_64),然后將這兩份合并成一個包后引入項目中進(jìn)行使用。在Intel機(jī)型上,真機(jī)上使用arm64指令,模擬器(x86_64)中使用x86_64指令,所以不存在問題。但是在M1機(jī)型上,模擬器是以arm64運行的,顯然再以x86_64運行就會出現(xiàn)問題。
有同學(xué)可能會想到包中是有arm64指令(真機(jī))的,拿給以arm64運行的模擬器使用不就可以了嗎?實際上xcode底層并不是這樣處理的,它真機(jī)就找真機(jī)的,模擬器就找模擬器的。
解決方案
常用方案
對于這類架構(gòu)報錯問題,網(wǎng)上的資料一般會告訴你兩個解決方案:
以Rosetta模式運行Xcode。
修改Build Settings -> Excluded Architectures選項,添加Any iOS Simulator SDK選項,并設(shè)置值為arm64。圖示如下:

這兩種方案都能解決編譯問題,但是也都存在問題。
在iOS12及以后,不再支持iphone5及以下機(jī)型,而后續(xù)的機(jī)型都是arm64架構(gòu),所以這里不再對之前的armv6/armv7/armv7s/i386 等指令集進(jìn)行說明。
Rosetta方案說明
以Rosetta模式運行是M1機(jī)器上x86軟件無法運行的解決方案,它會將x86指令轉(zhuǎn)譯成ARM指令運行,這種轉(zhuǎn)譯顯然是存在性能損耗的,損耗大概在20%~30%,不到萬不得已,不推薦使用這種方案。
Excluded Architectures方案說明
修改Excluded Architectures選項也有它的問題。字面意思是排除架構(gòu)的意思,我們設(shè)置在模擬器中排除arm64就能解決模擬器無法編譯arm64的問題。
這樣的設(shè)置能生效會讓人有點費解,我們知道,在intel機(jī)型上,模擬器本來就是以x86方式運行的,排除arm64毫無影響。但是在M1機(jī)型上,模擬器是以arm64方式運行的,排除了arm64反而能跑,這不是把我的智商摁在地上摩擦么?,但是蘋果就是這樣干的,當(dāng)在M1機(jī)型上,排除了模擬器的arm64架構(gòu)后,模擬器還是會以arm64的方式運行,但是模擬器中的app是以x86的方式運行的,對蘋果的這個騷操作我們不得不服。圖示如下:

這種情況下,模擬器和應(yīng)用會通過XPC進(jìn)行通信,雖然理論上不會有問題,但通信時間會比較長,導(dǎo)致一些依賴計時器判斷的邏輯會出問題,例如滑動手勢,加速度的判斷會出一些問題,導(dǎo)致模擬器里大部分情況下列表無法觸發(fā)慣性滾動。- by kem
其它問題
有時候在Excluded Architectures選項中排除了模擬器的arm64指令,依然無法編譯通過,那么一般是項目設(shè)置和cocoapods的設(shè)置不一致導(dǎo)致,設(shè)置為一致后一般可以解決問題。可以通過在Podfile中添加如下內(nè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)解
通過上述內(nèi)容,我們知道了問題的由來,它是由于項目中存在.a或.framework,它們提供的指令集不完整導(dǎo)致的。Apple對于這類問題,也提供了解決方案,請由我細(xì)細(xì)道來。
以Xcode13為例,在我們創(chuàng)建靜態(tài)庫時,選擇真機(jī)編譯出來的包只包含arm64指令,選擇模擬器編譯出來的會同時包含arm64和x86_64指令。我看一些網(wǎng)上的教程,教別人將模擬器部分的arm64移除,其實大可不必。因為要支持M1機(jī)器正常跑模擬器,模擬器必須同時包含arm64和x86_64指令。
2019年的WWDC,apple提供了一種新的框架封裝格式XCFramework。簡單理解就是以前使用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就是把兩個不同指令集的framework放入了同一個文件夾(.xcframework),并生成了一個配置文件Info.plist。這樣生成的XCFramework就可以完美的解決M1機(jī)器無法編譯模擬器的問題。
XCFramework的創(chuàng)建指令也很簡單:
# -- 針對.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機(jī)型無法編譯模擬器的關(guān)鍵就是針對模擬器的包要同時包含arm64和x86_64指令集。如果使用只支持x86_64指令集的模擬器包,就算打包成XCFramework也會依然存在這個問題。
后記
以現(xiàn)在的情況,很多第三方框架,并沒有使用XCFramework,而項目中只要有一個框架沒有支持模擬器的arm64指令,那么在M1機(jī)器上,模擬器只能以Rosetta模式運行應(yīng)用,對這一塊的普遍支持估計要等M1普及以后了。
參考資料
蘋果換芯,成了開發(fā)者們的噩夢?
armv6、armv7、armv7s、armv8、armv64及其i386、x86_64區(qū)別
細(xì)說iOS靜態(tài)庫和動態(tài)庫
關(guān)于Xcode11的XCFrameworks框架