使用 Xcode 建立一個 Framework

之前有介紹過如何建立一個 CocoaPods,而這篇文章則是紀錄一下這次直接在 Xcode 上建立 Framework 會遇上的問題。

建立 Framework 專案

首先我們先打開 Xcode,並選擇 iOS 類別底下的 Framework & Library 裡頭的 Framework

file

並依照所需要的欄位填寫相關資訊,這邊目前都和直接建立一個專案一致流程。

file

而如果你有打算在裡頭寫測試的話,就將 Include Unit Tests ☑️

Framework 開發

使用 Xcode 建立起來和 CocoaPods 創建,在開發上並沒有什麼差異;所以就依照著相對應的存取限制來設計你的 framework 即可。

這篇的重點會落在一些參數設定。

Mach-O Type 決定方式

在這個 framework 的 target 之中,我們可以從 Build Settings 的 Mach-O Type 來決定它是一個怎樣的 framework

file

而要如何取捨這個 framework 的輸出型態,可以依據一些使用方式來判斷

Static Library

  • 有使用到其他 static library
  • 使用 Singleton

Dynamic Library

  • 有包含其他檔案,如圖片、JSON 之類的

輸出成 .framework

file

輸出成 .framework 的方式很簡單,你只要 build 這個專案就會產生出相對應的 .framework 檔案;但重點會是,若你今天使用模擬器 build 的話,它會建立一個 Debug-iphonesimulator 的資料夾,裡頭包含只支援模擬器的 .framework,而使用 Generic iOS Device 的話,就會產生出 Debug-iphoneos 的資料夾,裡頭的 .framework 也只會支援實體裝置。

這邊可以透過 lipo 的指令將兩者融合成一個同時支援模擬器又支援實體裝置的 .framework,或者可以建立一個新的 target 做這件事👌

建一個 Aggregate 來跑 script

首先在 TARGETS 那邊點擊 + 來建立一個 Aggregate

file

並在它的 Build Phases 欄位中加入一個 Run script

#!/bin/sh

UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal

# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"

# Step 1. Build Device and Simulator versions
xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build

# Step 2. Copy the framework structure (from iphoneos build) to the universal folder
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"

# Step 3. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory
SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/."
if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then
cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"
fi

# Step 4. Create universal binary file using lipo and place the combined executable in the copied framework directory
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}"

# Step 5. Convenience step to copy the framework to the project's directory
cp -R "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" "${PROJECT_DIR}"

# Step 6. Convenience step to open the project's directory in Finder
open "${PROJECT_DIR}"

file

這樣在 build 這個 Aggregate target 時,就會產生出一個 universal 的 .framework 在專案裡頭了!

發表迴響