iOS - 關於使用 Mac Catalyst 技術時遇到的二進制庫連接問題分析及解決

前言

Apple 在 WWDC 2019 上宣佈了 Mac Catalyst 技術,其做用是將 UIKit 從 iOS 移植到 macOS 上。咱們能夠在 Xcode 11 及更高版本中使用 Mac Catalyst 技術來爲 iPad App 建立 Mac 版本,只需勾選一個複選框便可開啓這項技術,但實際上成功建立完 Mac 版本並不這麼輕鬆,咱們還須要解決編譯以及更多的適配問題。ios

在本篇文章中,咱們談一談過程當中基本上都會遇到的二進制庫連接問題 「building for Mac Catalyst, but linking in object file built for iOS Simulator」 及其解決方案。git

問題緣由

二進制庫中的 x86_64 架構指令是來自 iOS Simulator 的編譯產物,其不適用於 Mac Catalyst,致使在 Mac Catalyst 下編譯時連接出錯。github

解決方案

方案一:給二進制庫添加 Mac Catalyst 支持

須要考慮兩種狀況:macos

二進制庫繼續以 .a 或 .framework 的形式xcode

爲解決這一問題,咱們須要將 Mac 支持的架構合入到 fat 庫,但一般會遇到這樣的問題:markdown

... and ... have the same architectures (archName) and can't be in the same fat output file
複製代碼

意爲相同的架構指令不能合併到一個 fat 庫中。架構

就好比,Xcode12 以上編譯出的 iOS Simulator 是帶有 arm64 架構指令的,爲解決合成 fat 庫報以上錯誤問題,咱們需將 iOS Simulator 的 arm64 去除才能繼續執行合併操做。但如今,iOS Simulator 和 Mac Catalyst 的 x86_64 都是必須,該怎麼辦呢?解決方案是爲 Mac Catalyst 編譯出 x86_64h,這樣就能將其和 iOS Simulator 的 x86_64 進行合併。框架

可是,若是咱們直接使用 :iphone

xcodebuild  -project "xxx.xcodeproj" -scheme "xxx" -destination  "generic/platform=macOS,variant=Mac Catalyst, 
複製代碼

命令嘗試編譯出包含 x86_64h 的 Mac Catalyst 二進制庫的話,將會獲得錯誤:ide

None of the architectures in ARCHS (x86_64h) are valid. Consider setting ARCHS to $(ARCHS_STANDARD) or updating it to include at least one value from VALID_ARCHS (arm64, armv7, armv7s, x86_64). (in target 'xxx' from project 'xxx')
複製代碼

緣由是 Apple 的標準體系架構指令中默認是不包含 x86_64h 的。這就須要在 Build Settings 中給 Architectures 添加上 x86_64h,同時須要在 User-Defined > VALID_ARCHS 中添加上 x86_64h,由於該列表和 Architectures 列表的交集,纔是 Xcode 最終生成二進制包所支持的指令集。

完成後咱們將會在 Xcode 中看到設備多出了 My Mac (Intel (x86_64h)) 選項,這時候便可編譯出含 x86_64h 的二進制庫。

最後咱們就能夠合成包含 armv七、armv6四、i38六、x86_6四、x86_64h 的 fat 庫,這個庫將同時支持 iOS、iOS Simulator 和 Mac Catalyst。

二進制庫轉爲 xcframework 形式

相比較 fat 庫,更推薦的方式是建立包含 iOS、iOS Simulator 和 Mac Catalyst 三種 Framework 變體的 XCFramework。XCFramework 是由 Xcode 建立的可分發二進制框架,它包含 framework 或 library 的變體,以即可以在多個平臺(iOS、macOS、tvOS 和 watchOS)包括在 simulator 上使用。

XCFramework 是目前蘋果支持且推薦的一種二進制框架格式。XCFramework 相比 Framework 有不少優勢,感興趣的話能夠查閱相關資料。最後咱們所須要的 xcframework 文件是如下這樣的:

|____xxx.xcframework
| |____Info.plist
| |____ios-arm64_armv7
| | |____xxx.framework
| |____ios-arm64_i386_x86_64-simulator
| | |____xxx.framework
| |____ios-arm64_x86_64-maccatalyst
| | |____xxx.framework
複製代碼

方案二:閹割不支持的庫及功能

讓 Xcode 跳過那些不支持 macOS 架構的庫的連接和編譯階段,也就是功能閹割。這是必要的,由於對於自家的二進制庫能夠很輕鬆的提供 xcframework,但對於三方庫就不太好辦了,只能聯繫第三方來給咱們提供支持。還有就是有些庫自己就不適用於 Mac 端,好比三方分享、一鍵登陸等。這時候就須要針對 Mac 端對這些功能進行閹割,那具體要怎麼進行閹割呢?

一、對於 Apple 本身的庫,在咱們啓用 Mac Catalyst 技術時,Xcode 會盡量爲咱們項目的 Mac 構建版本自動排除不兼容的框架。

二、對於手動集成的庫,咱們能夠在 General - Frameworks, Libraries, and Embedded Content 中將它的 Platforms 設置爲 iOS only。

三、我想你們仍是比較關心經過 Cocoapods 集成的庫怎麼限制平臺。

對於動態庫

Pods - project - framework target - Build phases - Compile Sources 中將 .m 文件的 Platforms 設置爲 iOS only,以跳過它們的編譯。

對於靜態庫

因爲它們已經被編譯因此僅需連接便可。每當咱們運行 pod install 或 pod update 時,CocoaPods 都會爲咱們生成一個 .xcconfig 配置文件。這些文件保存在咱們的項目目錄下。

${PODS_ROOT}/Target Support Files/Pods-MyAppTargetName/Pods-MyAppTargetName.release.xcconfig
複製代碼

在該文件中咱們能夠看到如下配置內容:

OTHER_LDFLAGS = $(inherited) -ObjC -framework "FrameworkThatNotSupportsCatalyst" -framework "FrameworkThatSupportsCatalyst"
複製代碼

該配置內容所對應的就是 App Target - Build Settings - Linking - Other Linker Flags 的配置,咱們要作的事情更改這部份內容,將僅支持 iOS 和 iPadOS 的庫剝離出來,不讓其參與 Mac 平臺的連接。

OTHER_LDFLAGS = $(inherited) -ObjC -framework "FrameworkThatSupportsCatalyst"

OTHER_LDFLAGS[sdk=iphone*] = $(inherited) -ObjC -framework "FrameworkThatNotSupportsCatalyst"
複製代碼

流程自動化

你能夠在這裏獲取腳本 fermoya/remove_unsupported_libraries.rb,並將該腳本放在 Podfile 的同一目錄下。

而後在 Podfile 中添加如下內容:

# Podfile
load 'remove_unsupported_libraries.rb'
target 'My target' do   
    use_frameworks!   
    # Install your pods   pod 
    ...
end
def unsupported_pods_forMacCatalyst   
    ['Bugly', 'UMCAnalytics', 'QQSDK', ...]
end
def supported_pods_forMacCatalyst   
    []
end
post_install do |installer|   
    $verbose = true # remove or set to false to avoid printing
    installer.configure_support_catalyst(supported_pods_forMacCatalyst, unsupported_pods_forMacCatalyst)
end
複製代碼

該部份內容摘自:

閹割完成後,咱們還須要爲使用到這些庫的代碼包上如下宏,以讓 Xcode 在選擇 Mac 端編譯時忽略這些代碼,不然會在編譯時獲得許多的 Undefined symbol 錯誤。

// Swift
#if !targetEnvironment(macCatalyst) 
// Code to exclude from Mac. 
#endif

// Objective-C
#if !TARGET_OS_MACCATALYST  // or !TARGET_OS_UIKITFORMAC
// Code to exclude from Mac. 
#endif
複製代碼

寫在最後

使用 Mac Catalyst 技術來爲 iPad App 建立 Mac 版本的過程當中還有不少的適配問題須要處理,在研發收尾後我會發布一篇比較全面的,但願對你們有幫助。

相關文章
相關標籤/搜索