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