使用 Xcode 制作 Framework 與 XCFramework
最近公司有個(gè)項(xiàng)目外包,我就負(fù)責(zé)提供離在線語(yǔ)音識(shí)別 SDK 和數(shù)據(jù)埋點(diǎn) SDK 封裝,在制作 Framework 的過(guò)程中,遇到了很多問(wèn)題。所以在這篇文章里我們會(huì)主要介紹下 如何制作 Frameworks ,以及如何解決遇到的一些問(wèn)題。
編譯過(guò)程簡(jiǎn)述
在制作 Framework 之前,我想簡(jiǎn)單闡述下編譯器的工作原理,這有助于我們理解靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)的制作。如果想了解編譯器的詳細(xì)設(shè)計(jì),請(qǐng)點(diǎn)這里。
我們都知道,計(jì)算機(jī)沒(méi)法直接理解我們?nèi)祟?lèi)用高級(jí)語(yǔ)言寫(xiě)的程序,所以編譯器可以幫助我們將高級(jí)語(yǔ)言寫(xiě)的程序轉(zhuǎn)換成計(jì)算機(jī)能懂的二進(jìn)制。下面我們結(jié)合一個(gè)簡(jiǎn)單的 C 程序來(lái)講解下具體的編譯過(guò)程。
使用 GCC 編譯源程序
GCC(GNU Compiler Collection,GNU編譯器套件)是由GNU開(kāi)發(fā)的編程語(yǔ)言譯器。GNU編譯器套件包括C、C++、 Objective-C、 Fortran、Java、Ada和Go語(yǔ)言前端,也包括了這些語(yǔ)言的庫(kù)(如libstdc++,libgcj等。)
GCC 的初衷是為 GNU 操作系統(tǒng)專(zhuān)門(mén)編寫(xiě)的一款編譯器。GNU 系統(tǒng)是徹底的自由軟件,此處“自由”的含義是它尊重用戶的自由。
編寫(xiě)源程序
我們先編寫(xiě)一個(gè)簡(jiǎn)單的 C 程序,然后存儲(chǔ)為 hello.c 源文件。
#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
GCC 編譯
-
編寫(xiě)好源程序后,在控制終端輸入以下命令,gcc 自動(dòng)會(huì)完成所有編譯過(guò)程,最后輸出可執(zhí)行文件
hello。gcc -o hello hello.c -
在終端運(yùn)行可執(zhí)行文件
hello,終端就會(huì)輸出hello, world。./hello
我們可以看到,一個(gè) gcc 命令就將源程序轉(zhuǎn)變成了可執(zhí)行文件,它使用源程序編譯變得非常簡(jiǎn)單。但我們還是得深究下,gcc 究竟做了些啥事。接下來(lái),我們就簡(jiǎn)單介紹下編譯器的四個(gè)階段:預(yù)處理、編譯、匯編,以及鏈接。

預(yù)處理階段
CPP,即 C Pre-Processor, 即 C 預(yù)處理器,是一個(gè)獨(dú)立于C 編譯器的小程序,預(yù)編譯器并不理解 C 語(yǔ)言語(yǔ)法,它僅是在程序源文件被編譯之前,實(shí)現(xiàn)文本替換的功能。
拿 hello.c 源程序?yàn)槔?,CPP 會(huì)根據(jù) <font color=green>#</font> 開(kāi)頭的指令來(lái)修改源程序。比如 hello.c 中的第一行 <font color=green>#include <stdio.h></font> 就是告訴預(yù)處理器去把系統(tǒng)頭文件 <font color=green>stdio.h</font> 內(nèi)容讀取出來(lái),并直接插入到 hello.c 源程序中來(lái),替換 <font color=green>#include <stdio.h></font>。
修改后的 C 程序一般另保存為 <font color=green>.i</font> 后綴的文本文件(本例為 hello.i),輸出的 hello.i 將用于下一個(gè)階段。
編譯階段
接下來(lái)編譯器 cc1 將 hello.i 編譯成匯編程序,并保存為 <font color=green>.s</font> 后綴的匯編語(yǔ)言文本文件(本例為 hello.s)。
編譯成匯編語(yǔ)言程序有個(gè)好處,就是對(duì)于不同的編譯器,不同的高級(jí)語(yǔ)言,都會(huì)編譯輸出一樣的匯編程序。
例如,C 編譯器 和 Fortran 編譯器,編譯后都會(huì)輸出同樣的匯編程序。
匯編階段
目標(biāo)文件 (Object File)
在介紹匯編階段前,我們先了了解下目標(biāo)文件(Object File), 它其實(shí)有 3 種形式:
-
可重定位目標(biāo)文件 (Relocatable Object File)
包含可與其它
relocatable object file相結(jié)合的二進(jìn)制代碼和數(shù)據(jù),由編譯器和匯編器產(chǎn)生。
-
可執(zhí)行文件 (Executable Object File)
包含可直接復(fù)制到內(nèi)存并執(zhí)行的二進(jìn)制代碼和數(shù)據(jù),由鏈接器生成。
-
共享目標(biāo)文件 (Shared Object File)
一種特殊的
relocatable object file,它可以被裝載入內(nèi)存,并且可以在裝載或運(yùn)行的時(shí)候動(dòng)態(tài)地鏈接。由編譯器和匯編器產(chǎn)生。
匯編階段的工作內(nèi)容
匯編階段的工作就是將匯編文本程序翻譯成機(jī)器指令,輸出目標(biāo)文件。
匯編器 (as) 將匯編程序 hello.s 翻譯成機(jī)器指令,然后包裝成可重定位目標(biāo)程序(Relocatable Object Program),并將其結(jié)果保存在 hello.o 文件中。hello.o 為二進(jìn)制文件。
鏈接階段
在我們的 hello 程序中,它調(diào)用了C 標(biāo)準(zhǔn)庫(kù)中 <font color=green>printf</font> 方法,而 <font color=green>printf</font> 位于事先編譯好的獨(dú)立目標(biāo)文件 <font color=green>printf.o</font> 中。所以為了能讓 hello 程序運(yùn)行起來(lái),我們需要采取某種手段將 <font color=green>printf.o</font> 合并入 <font color=green>hello.o</font>。幸運(yùn)的是,鏈接器(ld) 就是做這一工作。
經(jīng)過(guò)鏈接器的合并操作后,輸出 <font color=green>hello</font> 可執(zhí)行文件。在終端命令行中輸入 ./hello 回車(chē)后, <font color=green>hello</font> 可執(zhí)行文件被裝載入內(nèi)存(通過(guò)加載器Loader來(lái)完成),并由系統(tǒng)執(zhí)行,程序就跑起來(lái)了。
Library
我們?cè)陂_(kāi)發(fā)過(guò)程中,會(huì)把一些通用的函數(shù)制作成一個(gè)庫(kù),或者將一個(gè)功能模塊制作成一個(gè)庫(kù) ,然后提供給 App 使用,可以達(dá)到共享復(fù)用的目的。
Library 可以理解成目標(biāo)文件的集合,將相關(guān)的目標(biāo)文件打包在一起,就成了一個(gè) Library。我們按照 Library 是如何鏈接到 App 中的,可以把 Library 分成靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)。
靜態(tài)庫(kù)(Static Libraries)
下圖為 App 用到了靜態(tài)庫(kù)的情況。App 自身的代碼被編譯成目標(biāo)文件后,通過(guò)靜態(tài)鏈接器將App的目標(biāo)文件與靜態(tài)庫(kù)合并,并生成的可執(zhí)行文件。這樣,App 自身代碼生成的目標(biāo)文件與靜態(tài)庫(kù)都被拷貝到可執(zhí)行文件中,從而靜態(tài)庫(kù)也成為了 App 可執(zhí)行文件的一部分。這樣的庫(kù)呢,我們稱(chēng)之為 靜態(tài)庫(kù),也稱(chēng)為 <I>static archive libraries</I>, 或 <I>static linked shared libraries</I>.

存在形式
靜態(tài)庫(kù)主要以 .a, .lib 的形式存在,在蘋(píng)果生態(tài)系統(tǒng)中,還可以是 .framework 或 .xcframework 。
特點(diǎn)
App 啟動(dòng)的時(shí)候就全部載入內(nèi)存空間,所以在 App 運(yùn)行過(guò)程中,需要使用依賴(lài)庫(kù)的時(shí)候不需要額外從外部加載,速度快,但也增加了 App 的啟動(dòng)時(shí)間。
App 的可執(zhí)行文件變大,占用內(nèi)存也會(huì)相應(yīng)增多。因?yàn)锳pp依賴(lài)的所有靜態(tài)庫(kù)都會(huì)被靜態(tài)鏈接器鏈接并拷貝到將要生成的 app 可執(zhí)行文件中。
當(dāng)靜態(tài)庫(kù)需要修改時(shí),必須得重新編譯和發(fā)布靜態(tài)庫(kù),所以不便于維護(hù)。
動(dòng)態(tài)庫(kù)(Dynamic Library)
動(dòng)態(tài)庫(kù)又 dynamic shared libraries, shared objects, or dynamically linked libraries。我們以 OS X 為例,當(dāng) App 啟動(dòng)時(shí),操作系統(tǒng)內(nèi)核會(huì)將 App 代碼和數(shù)據(jù)載入新進(jìn)程(也就是操作系統(tǒng)為 App 創(chuàng)建的新進(jìn)程)的地址空間。與此同時(shí)呢,操作系統(tǒng)內(nèi)核也會(huì)把動(dòng)態(tài)加載器(Dynamic Loader) 載入進(jìn)程,由動(dòng)態(tài)加載器來(lái)完成加載 App 依賴(lài)的動(dòng)態(tài)庫(kù)。不過(guò)在啟動(dòng)階段,動(dòng)態(tài)加載器只會(huì)根據(jù)靜態(tài)鏈接器中記錄的 App 已鏈接的依賴(lài)庫(kù)的名字,然后使用依賴(lài)庫(kù)的 install name 來(lái)查找它們是否在文件系統(tǒng)中存在。如果不存在或不兼容,App 啟動(dòng)過(guò)程會(huì)中斷。動(dòng)態(tài)庫(kù)被完全載入內(nèi)存,是在代碼里使用它的時(shí)候。所以相對(duì)靜態(tài)庫(kù)來(lái)說(shuō),使用動(dòng)態(tài)庫(kù)鏈接的 App 啟動(dòng)過(guò)程會(huì)更快。

存在形式
動(dòng)態(tài)庫(kù)主要以 .dylib,.so,dll 的形式存在,在蘋(píng)果生態(tài)系統(tǒng)中,還可以是 .framework 或 .xcframework 。
iOS App 的動(dòng)態(tài)庫(kù)存放在 .app bundle 下的 Frameworks 文件夾。
特點(diǎn)
App 按需裝載,可以加速 App 的啟動(dòng)。
動(dòng)態(tài)庫(kù)不會(huì)被拷貝到 App 的可執(zhí)行文件中,所以可以動(dòng)態(tài)按需加載。
動(dòng)態(tài)庫(kù)的維護(hù)和更新很方便,只要 APIs 不變,依賴(lài)動(dòng)態(tài)庫(kù)的 App 就不用重新編譯 。因?yàn)閯?dòng)態(tài)庫(kù)并不是 App 可執(zhí)行文件的一部分,是獨(dú)立的,可動(dòng)態(tài)加載的。
Apple FrameWorks
Framework
Framework 可以通俗的理解為封裝了共享資源的具有層次結(jié)構(gòu)的文件夾。共享資源可以是 nib文件、國(guó)際化字符串文件、頭文件、庫(kù)文件等等。它同時(shí)也是個(gè) Bundle,里面的內(nèi)容可以通過(guò) Bundle 相關(guān) API 來(lái)訪問(wèn)。Framework 可以是 static framework 或 dynamic framework。<font color=red> 在 iOS App 打包完成后,如果 Framework 包含了模擬器指令集(x86_64 或 i386),那么用 Xcode 發(fā)布 App 的時(shí)候,會(huì)報(bào) unsupported architectures 的錯(cuò)誤,所以需要我們手動(dòng)或腳本去移除。</font>
XCFramework
XCFramework 是由 Xcode 創(chuàng)建的一個(gè)可分發(fā)的二進(jìn)制包,它包含了 framework 或 library 的一個(gè)或多個(gè)變體,因此可以在多個(gè)平臺(tái)(iOS、macOS、tvOS、watchOS) 上使用,包括模擬器。XCFramework 可以是靜態(tài)的,也可以是動(dòng)態(tài)的。xcframework 的好處就是用 Xcode 發(fā)布的時(shí)候,Xcode 會(huì)自動(dòng)選用正確的指令集 Frameworks,省去了手動(dòng)移除動(dòng)態(tài)庫(kù)中的模擬器指令集的工作。<font color=red>不過(guò)值得注意的是,Xcode 11 才引入 XCFramework 。</font>
制作 Frameworks
關(guān)于如何用 Xcode 一步一步創(chuàng)建 Framework 工程的話,我就不多說(shuō)了,網(wǎng)上一大把教程,您也可以參考 Building Cross Platform Universal Frameworks 或 Swift Cross Platform Framework。我重點(diǎn)講如何用腳本來(lái)制作各種類(lèi)型的 frameworks。為什么要介紹腳本呢,網(wǎng)上不是很多腳本制作 frameworks 嗎? 剛開(kāi)始我也是直接用網(wǎng)上的腳本,可總會(huì)有這樣那樣的問(wèn)題,各種錯(cuò)誤,所以決定在參考大神們文章的同時(shí),自己重新整理下。
到這里,我假設(shè)您的 framework 代碼都已經(jīng)寫(xiě)好了,打包的 Aggregation Target 也創(chuàng)建好了。接下來(lái),我將直接講用腳本來(lái)制作 frameworks 。至于是制作 static framework 還是 dynamic framework 可以在 framework target 的 Build Settings 中的 Mach-O Type 選擇 framework 的類(lèi)型,一般選用 Dynamic Library 或者 Static Library 就行。
開(kāi)發(fā)環(huán)境
本文中使用的開(kāi)發(fā)環(huán)境為:
- macOS Catalina 10.15.4
- Xcode 11.5
.xcarchive 目錄結(jié)構(gòu)
在制作 universal framework 與 xcframework,我們都會(huì)用到 .xcarchive 包,所以我們先來(lái)看下它的目錄結(jié)構(gòu)。
<img src="https://gitee.com/evanxlh/Resources/raw/master/blog/make-frameworks-xcode/xcarchive-contents.png" alt="xcarchive contents" style="zoom:67%;" />
制作 Universal Framework 腳本
你可以在這里直接獲取制作 universal framwork 的完整腳本。
編譯單個(gè)平臺(tái)的函數(shù)
# 制作完 framework 后,是否在 Finder 中打開(kāi)
REVEAL_FRAMEWORK_IN_FINDER=true
# Framework 的名字
FREAMEWORK_NAME="${PROJECT_NAME}"
# 制作好的 framework 會(huì)輸出到這個(gè)文件夾下面
FREAMEWORK_OUTPUT_DIR="${PROJECT_DIR}/Distribution"
# Device Archive 生成的 .xcarchive 存放路徑。在工程的根目錄下生成 Build 文件夾。
ARCHIVE_PATH_IOS_DEVICE="./Build/ios_device.xcarchive"
# Simulator Archive 生成的 .xcarchive 存放路徑。
ARCHIVE_PATH_IOS_SIMULATOR="./Build/ios_simulator.xcarchive"
# 我們可以編譯更多平臺(tái)的 xcarchive
# ARCHIVE_PATH_MACOS="./build/macos.xcarchive"
# 生成單個(gè)平臺(tái)的 .xcarchive. 接收4個(gè)參數(shù), scheme, destination, archivePath,指令集.
# xcpretty 可以刪除,這里用來(lái)使 Xcode 輸出的日志更加人性化。
function archiveOnePlatform {
echo "? Starts archiving the scheme: ${1} for destination: ${2};\n? Archive path: ${3}"
xcodebuild archive \
-scheme "${1}" \
-destination "${2}" \
-archivePath "${3}" \
VALID_ARCHS="${4}" \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES | xcpretty
# BUILD_LIBRARY_FOR_DISTRIBUTION=YES
# sudo gem install -n /usr/local/bin xcpretty
# xcpretty makes xcode compile information much more readable.
}
編譯所有平臺(tái)的函數(shù)
以下方法可以編譯并生成 iOS device, simulator 兩個(gè)平臺(tái)的 .xcarchive,此方法接收一個(gè)參數(shù):scheme, 即 對(duì)應(yīng) app target 的 scheme。通常情況下,scheme 和 framework name 是相同的。
function archiveAllPlatforms {
# https://www.mokacoding.com/blog/xcodebuild-destination-options/
# Platform Destination
# iOS generic/platform=iOS
# iOS Simulator generic/platform=iOS Simulator
# iPadOS generic/platform=iPadOS
# iPadOS Simulator generic/platform=iPadOS Simulator
# macOS generic/platform=macOS
# tvOS generic/platform=tvOS
# watchOS generic/platform=watchOS
# watchOS Simulator generic/platform=watchOS Simulator
# carPlayOS generic/platform=carPlayOS
# carPlayOS Simulator generic/platform=carPlayOS Simulator
SCHEME=${1}
archiveOnePlatform $SCHEME "generic/platform=iOS Simulator" ${ARCHIVE_PATH_IOS_SIMULATOR} "x86_64"
archiveOnePlatform $SCHEME "generic/platform=iOS" ${ARCHIVE_PATH_IOS_DEVICE} "armv7 arm64"
# archiveOnePlatform $SCHEME "generic/platform=macOS" ${ARCHIVE_PATH_MACOS}
}
這個(gè)方法執(zhí)行完后,在本例中會(huì)得到以下 Build 文件夾內(nèi)的內(nèi)容:
<img src="https://gitee.com/evanxlh/Resources/raw/master/blog/make-frameworks-xcode/xcarchives.png" alt="Generated xcarchives" style="zoom:67%;" />
生成 Universal Framework的函數(shù)
function makeUniversalFramework {
# xcarchive 包中的 Frameworks 目錄相對(duì)路徑
FRAMEWORK_RELATIVE_PATH="Products/Library/Frameworks"
# 接下來(lái)的三個(gè)路徑分別是模擬器平臺(tái)的framework路徑,真機(jī)平臺(tái)的framework路徑,以及輸出的universal framework路徑
SIMULATOR_FRAMEWORK="${ARCHIVE_PATH_IOS_SIMULATOR}/${FRAMEWORK_RELATIVE_PATH}/${FREAMEWORK_NAME}.framework"
DEVICE_FRAMEWORK="${ARCHIVE_PATH_IOS_DEVICE}/${FRAMEWORK_RELATIVE_PATH}/${FREAMEWORK_NAME}.framework"
OUTPUT_FRAMEWORK="${FREAMEWORK_OUTPUT_DIR}/${FREAMEWORK_NAME}.framework"
mkdir -p "${OUTPUT_FRAMEWORK}"
# Copy all the contents of iphoneos framework to output framework dir.
cp -rf "${DEVICE_FRAMEWORK}/." "${OUTPUT_FRAMEWORK}"
lipo "${SIMULATOR_FRAMEWORK}/${FREAMEWORK_NAME}" "${DEVICE_FRAMEWORK}/${FREAMEWORK_NAME}" \
-create -output "${OUTPUT_FRAMEWORK}/${FREAMEWORK_NAME}"
# For Swift framework, Swiftmodule needs to be copied in the universal framework
if [ -d "${SIMULATOR_FRAMEWORK}/Modules/${FRAMEWORK_NAME}.swiftmodule/" ]; then
cp -f "${SIMULATOR_FRAMEWORK}/Modules/${FRAMEWORK_NAME}.swiftmodule/*" "${OUTPUT_FRAMEWORK}/Modules/${FRAMEWORK_NAME}.swiftmodule/" | echo
fi
if [ -d "${DEVICE_FRAMEWORK}/Modules/${FRAMEWORK_NAME}.swiftmodule/" ]; then
cp -f "${DEVICE_FRAMEWORK}/Modules/${FRAMEWORK_NAME}.swiftmodule/*" "${OUTPUT_FRAMEWORK}/Modules/${FRAMEWORK_NAME}.swiftmodule/" | echo
fi
}
開(kāi)始制作
echo "#####################"
echo "? Cleaning Framework output dir: ${FREAMEWORK_OUTPUT_DIR}"
rm -rf "$FREAMEWORK_OUTPUT_DIR"
echo "? Archive framework: ${FREAMEWORK_NAME}"
archiveAllPlatforms "$FREAMEWORK_NAME"
echo "? Make universal framework: ${FREAMEWORK_NAME}.framework"
makeUniversalFramework
# Clean Build
rm -rf "./Build"
if [ ${REVEAL_FRAMEWORK_IN_FINDER} = true ]; then
open "${FREAMEWORK_OUTPUT_DIR}/"
fi
去除動(dòng)態(tài)庫(kù)中的模擬器指令集
正如之前提到的,App 打包過(guò)程中,需要將 App 依賴(lài)的動(dòng)態(tài)庫(kù)中的模擬器指令集去除,這里是完整的腳本, 如何使用,請(qǐng)點(diǎn) Stripping unwanted architectures from dynamic libraries。在 app target 的 Build Phase 下新建 Run Script,并放到 Embed Frameworks 下面,然后將腳本復(fù)制進(jìn)去就行。
#!/bin/sh
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
echo $APP_PATH
# This script loops through the frameworks embedded in the application and
# removes unused architectures.
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"
EXTRACTED_ARCHS=()
for ARCH in $ARCHS
do
echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"
lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
done
echo "Merging extracted architectures: ${ARCHS}"
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
rm "${EXTRACTED_ARCHS[@]}"
echo "Replacing original executable with thinned version"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
done
制作 XCFramework 腳本
生成 Universal Framework的函數(shù)
你可以在這里直接獲取制作 xcframwork 的完整腳本。編譯單個(gè)平臺(tái)和全部平臺(tái)的函數(shù)跟制作 Framework 一樣,所以這里直接列出制作 xcframework 的函數(shù)。<font color=red>制作 XCFramewrok 的時(shí)候,需要將 Build Settings 中的 Build Libraries for Distribution 設(shè)置為 YES。不過(guò)即使不設(shè)置,腳本也會(huì)將其設(shè)置為 YES。</font>
function makeXCFramework {
FRAMEWORK_RELATIVE_PATH="Products/Library/Frameworks"
OUTPUT_DIR="${FREAMEWORK_OUTPUT_DIR}/DynamicFramework"
mkdir -p "${OUTPUT_DIR}"
xcodebuild -create-xcframework \
-framework "${ARCHIVE_PATH_IOS_DEVICE}/${FRAMEWORK_RELATIVE_PATH}/${FREAMEWORK_NAME}.framework" \
-framework "${ARCHIVE_PATH_IOS_SIMULATOR}/${FRAMEWORK_RELATIVE_PATH}/${FREAMEWORK_NAME}.framework" \
-output "${OUTPUT_DIR}/${FREAMEWORK_NAME}.xcframework"
}
開(kāi)始制作
echo "#####################"
echo "? Cleaning XCFramework output dir: ${FREAMEWORK_OUTPUT_DIR}"
rm -rf $FREAMEWORK_OUTPUT_DIR
#### Make XCFramework
echo "? Archive framework: ${FREAMEWORK_NAME}"
archiveAllPlatforms $FREAMEWORK_NAME
echo "? Make framework: ${FREAMEWORK_NAME}.xcframework"
makeXCFramework
# Clean Build
rm -rf "./Build"
if [ ${REVEAL_XCFRAMEWORK_IN_FINDER} = true ]; then
open "${FREAMEWORK_OUTPUT_DIR}/"
fi
最后生成的 xcframework 長(zhǎng)這個(gè)樣子:
<img src="https://gitee.com/evanxlh/Resources/raw/master/blog/make-frameworks-xcode/xcframe.png" alt="Generated xcarchives" style="zoom:67%;" />
小技巧
-
查看 framework 包含的指令集
lipo -info /path/to/xxx.framework/xxx -
查看 dynamic framework 是否支持 bitcode
otool -arch armv7 -l /path/to/xxx.framework/xxx | grep __bundle如果包含 bitcode, 你會(huì)看到 <font color=red>sectname __bundle</font> 信息。如果不包含,Terminal 輸出為空。
-
查看 static framework 是否支持 bitcode
otool -arch armv7 -l /path/to/xxx.framework/xxx | grep __bitcode如果包含 bitcode, 你會(huì)看到 <font color=red>sectname __bitcode</font> 信息。如果不包含,Terminal 輸出為空。
參考
非常感謝以下文章的貢獻(xiàn)者,使我對(duì)編譯原理相關(guān)知識(shí),以及對(duì)靜態(tài)庫(kù)、動(dòng)態(tài)庫(kù)、framework 有了更深刻的認(rèn)識(shí)。
GCC
GCC, the GNU Compiler Collection
Apple Frameworks
Dynamic Library Programming Topics
Code Loading Programming Topics
Stripping Unwanted Architectures From Dynamic Libraries In Xcode