iOS使用Swift制作Framework(開發、調試、打包)

前言

已經3年沒碰過iOS了,前段時間從朋友那接手了一個快要開發完成的SDK項目,一開始我的內心其實是拒絕的,主要原因是:項目是用Swift開發的,我本身對Swift不熟悉,之前做iOS開發還是用的Objective-C,很久不做了,擔心自己難堪重任。除此之外我還有點手癢,甚至還有一絲絲期待完工后朋友的贊賞,再加上朋友公司比較急,也實在找不到合適的人,天意如此。沒想到一頓操作下來,遇到的坑不計其數,看樣子是真的老了。拿到項目,項目目錄是這樣的,項目中沒有引用的第三方庫,也沒有使用CocoaPods管理,項目根本跑不起來,我的難受瞬間開啟了......為了他人避坑也方便自己日后查閱,本文記錄了在開發、調試、打包的工程配置及遇到的問題,編碼和架構層面就不過多贅述了,謹以此文獻給那些年我們一起擼過代碼的同學、朋友和同事,如有錯誤,歡迎指正。


一、創建Framework工程

1.選擇iOS?-> Framework,輸入SDK名稱(XXSDK,下文所出現XXSDK都是指輸入的SDK名稱,在拷貝代碼時注意需要將XXSDK替換為實際SDK名稱)

2.修改Framework工程配置

設置Framework最低支持的iOS版本

General?-> XXSDK?-> Deployment Info,選擇iOS版本


設置Build Configuration(編譯環境)

Target選擇框?-> Edit Scheme...?-> Run?-> Info?-> Build Configuration?-> Release

上架應用時,app中引用的Framework必須要是Release環境,Debug環境只能調試使用,Release環境的Framework也可調試,為了方便,這里直接選擇Release

設置Build Active Architecture Only(僅構建當前選擇設備的架構)

General?-> XXSDK?->?Build Settings ->?Build Active Architecture Only,選擇No,表示不僅僅構建當前選擇設備的架構

設置Excluded Architecture(排除架構)

General?-> XXSDK?->?Build Settings ->?Excluded Architectures?-> Release ->?添加選擇Any iOS Simulator SDK?-> 添加arm64

因為Xcode12以后,構建模擬器的庫也默認支持arm64架構,這將會導致真機和模擬器的庫合并時失敗(fatal error: xx1/XLTestFrame and xx2/XLTestFrame have the same architectures (arm64) and can't be in the same fat output file),報錯原因:真機庫和模擬器庫都包含arm64架構


設置Mach-O Type(庫類型)

General?-> XXSDK?->?Build Settings ->?Mach-O Type?-> Static Library

Static Library表示靜態庫

3.使用CocoaPods管理第三方庫

打開XXSDK根目錄?-> pod init,打開Podfile文件,添加XXSDK用到的第三方庫,然后pod install,打開XXSDK.xcworkspace,開始編碼。注意:此時只是單純引用第三方庫,打包時并不會把第三方庫添加到XXSDK中


4.創建代碼文件,引入資源文件

右鍵New Group,輸入Support.bundle,然后將資源文件直接拖到該文件夾下,這樣一來,該資源文件會自動在XXSDK?-> Build Phases?-> Copy Bundle Resources中添加上,使用時也比較方便,例如:

if let main = Bundle.main.path(forResource: "XXSDK", ofType: "framework") {? ? ? ? ? ? let bundle = Bundle.init(path: main)? ? ? ? ? ? let Image = UIImage.init(named: "ic_success.png", in: bundle, compatibleWith: nil)? ? ? ? }

除此之外,也可使用New File...?-> Settings Bundle的方式引入資源文件,不過在使用時需要多加一級bundle路徑才可找到對應的資源文件,此處不多贅述


二、添加編譯腳本,自動合并Framework

在使用腳本自動打包、合并之前,先介紹下手動打包、合并的過程,以便對整個流程有深刻印象。

使用Swift制作的Framework合并分3步

1.利用lipo命令合并XXSDK.framework/XXSDK

2.合并XXSDK.framework/Modules/XXSDK.swiftmodule下的文件以及XXSDK.framework/Modules/XXSDK.swiftmodule/Project下的文件

3.合并XXSDK.framework/Headers/XXSDK-Swift.h頭文件中的代碼

在我能查到的資料中,都只提到了第1步,血的教訓告訴我,2、3步必須要做,否則當把SDK集成到主工程時,會有各種奇怪報錯,關于報錯事項和原因以后再說。

lipo命令(簡單介紹下,都能查到):

1.lipo -info XXSDK:查看靜態庫支持的架構

2.lipo -create XXSDK1 XXSDK2 -output XXSDK:合并靜態庫,輸出一個新的庫支持前者在一起的所有架構

3.lipo XXSDK?-thin x86_64 -output XXSDK1:靜態庫拆分,輸出一個新的庫只支持x86_64架構

4.lipo XXSDK?-remove arm64 -output XXSDK1:靜態庫移除架構:輸出一個新的庫支持前者除了arm64以外的架構

1.添加編譯腳本的Target

TARGETS?-> +?-> Aggregate?-> 輸入名稱,然后選中新增的TARGET?-> Build Phases?-> +?-> New Run Script Phase



2.添加自動打包、合并的腳本文件

在項目的根目錄下,新增腳本文件frameworkAggregate.sh,編輯frameworkAggregate.sh內容,將腳本文件添加上


3.運行SDKAggregate,在腳本執行結束后,會自動在項目的根目錄下創建Products文件夾并打開


腳本代碼參考:Swift+第三方庫的framework制作流程詳解,如有侵權,請聯系刪除

并對腳本代碼作了3處調整,主要原因:

1.Xcode版本問題,在我本機的Xcode(13.3)上,會報這個錯:

error: unable to attach DB: error: accessing build database

代碼調整如下:

在xcodebuild命令下刪除clean并且加上OBJROOT="${OBJROOT}/DependentBuilds"參數

xcodebuild -workspace "${WORKSPACE_NAME}" -scheme "${SDK_NAME}" -configuration ${CONFIGURATION} -sdk iphoneos ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build

改成:

xcodebuild -workspace "${WORKSPACE_NAME}" -scheme "${SDK_NAME}" -configuration ${CONFIGURATION} OBJROOT="${OBJROOT}/DependentBuilds" -sdk iphoneos ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" build

xcodebuild -workspace "${WORKSPACE_NAME}" -scheme "${SDK_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build

改為:

xcodebuild -workspace "${WORKSPACE_NAME}" -scheme "${SDK_NAME}" -configuration ${CONFIGURATION} OBJROOT="${OBJROOT}/DependentBuilds" -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" build

2.關于上面介紹的Swift制作的Framework合并分3步的第三步

swift_header="$dir_path/Headers/${SDK_NAME}-Swift.h"

newline1="#if defined(__x86_64__) && __x86_64__ || (__arm64__) && __arm64__ || (__i386__) && __i386__"

sed -i '' 's/#if 0//g' $swift_header

sed -i '' 's/#elif defined(__arm64__) && __arm64__//g' $swift_header

sed -i '' "1 a\\ $newline1" $swift_header

完整腳本代碼如下:

#!/bin/sh # SDK名字, 改成自己的SDK名字即可

SDK_NAME=XXSDK ?

# framework最后輸出的路徑的文件夾

UNIVERSAL_OUTPUTFOLDER="${SRCROOT}/Products/" ?

WORKSPACE_NAME=${PROJECT_NAME}.xcworkspace ?

# 創建輸出路徑文件夾

mkdir -p "${UNIVERSAL_OUTPUTFOLDER}" ?

# 移除上次編譯生成的framework

rm -rf "${UNIVERSAL_OUTPUTFOLDER}/${SDK_NAME}.framework"

# 編譯真機版framework

xcodebuild -workspace "${WORKSPACE_NAME}" -scheme "${SDK_NAME}" -configuration ${CONFIGURATION} OBJROOT="${OBJROOT}/DependentBuilds" -sdk iphoneos ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" build ?

# 編譯模擬器版framework

xcodebuild -workspace "${WORKSPACE_NAME}" -scheme "${SDK_NAME}" -configuration ${CONFIGURATION} OBJROOT="${OBJROOT}/DependentBuilds" -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" build ?

# 拷貝編譯生成的真機版framework到最終輸出的路徑

cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${SDK_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}" ?

# 將模擬器框架的swift模塊復制到最終輸出的路徑

SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${SDK_NAME}.framework/Modules/${SDK_NAME}.swiftmodule/."

if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then

cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${SDK_NAME}.framework/Modules/${SDK_NAME}.swiftmodule"

fi ?

# 合并模擬器和真機framework, 生成通用framework

lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${SDK_NAME}.framework/${SDK_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${SDK_NAME}.framework/${SDK_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${SDK_NAME}.framework/${SDK_NAME}" ?

# 刪除編譯之后生成的無關的配置文件

dir_path="${UNIVERSAL_OUTPUTFOLDER}/${SDK_NAME}.framework/"

for file in ls $dir_path

do

if [[ ${file} =~ ".xcconfig" ]] then

rm -f "${dir_path}/${file}"

fi

done

# 拼接項目名.framework/Headers/項目名-Swift.h 文件名 swift_header="$dir_path/Headers/${SDK_NAME}-Swift.h"

# 在項目名.framework/Headers/項目名-Swift.h里面修改內容

newline1="#if defined(__x86_64__) && __x86_64__ || (__arm64__) && __arm64__ || (__i386__) && __i386__"

#查找#if 0替換成空

sed -i '' 's/#if 0//g' $swift_header

#查找#elif defined(__arm64__) && __arm64__替換成空

sed -i '' 's/#elif defined(__arm64__) && __arm64__//g' $swift_header

#在1第一行添加字符串newline1

sed -i '' "1 a\\ $newline1" $swift_header ?

# 打開合并后的文件夾 open "${UNIVERSAL_OUTPUTFOLDER}"

三、創建開發調試Demo工程

1.創建Project


選擇App,輸入Demo工程名稱,選擇SDK根目錄,加入指定workspace



2.編輯Podfile文件,保證SDK和Demo工程依賴相同的第三方庫,然后在SDK根目錄下執行pod install

platform :ios, '9.0'

workspace 'XLTestFrame.xcworkspace'

#公用pods

def commonPods ?

? use_frameworks! ?

? pod 'Alamofire' ??

end

# sdk項目

target 'XLTestFrame' do ?

? project 'XLTestFrame.xcodeproj' ?

? commonPods

end

# demo項目

target 'XLTestFrameDemo' do ?

? project 'XLTestFrameDemo/XLTestFrameDemo.xcodeproj' ?

? commonPods

end

此時,文件夾、項目結構分別如下



2.Demo工程關聯Framework及資源文件


這里將framework和資源文件引入到Demo工程中,保證Demo中可以訪問到framework的api及資源文件,同時也保證編譯Demo時也編譯framework,使Demo可以調用到framework里剛加的代碼。

在XXSDK中寫一些調試代碼,在Demo工程調用如下


XXSDK代碼


Demo使用SDK的api

寫在最后

在天朝有個現象,當遇到問題去嘗試搜索答案時,會看到很多一模一樣的文章,但是作者不是同一個人,更離譜的是明明答案是錯誤的,大家卻都還如此一致,所以便有了這篇文章,本篇是專題“iOS回憶錄”的第一篇,以后可能會有第二篇、三篇......只希望可以給新手一點點借鑒,如有錯誤,歡迎指正,各位大神輕噴。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容