使用 Xcode 制作 Framework 與 XCFramework

使用 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ù)處理、編譯、匯編,以及鏈接。

編譯過(guò)程

預(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)編譯器 cc1hello.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>.

Link Static Libraries

存在形式

靜態(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ì)更快。

Dynamic Link

存在形式

動(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 frameworkdynamic 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 FrameworksSwift 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 targetBuild 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 frameworkxcframework,我們都會(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 targetscheme。通常情況下,schemeframework 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 targetBuild 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 百科

GCC, the GNU Compiler Collection

C Pre-Processor

Compiler Design Overview

Apple Frameworks

Dynamic Library Programming Topics

Mach-O Programming Topics

Code Loading Programming Topics

Framework Programming Guide

Stripping Unwanted Architectures From Dynamic Libraries In Xcode

XcodeBuild Destination

Xcode Build Settings

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容