目前我們公司所有的SDK都采用了framework的方式,不再采取靜態(tài)庫(kù)的姿勢(shì),所以我今天也利用工作之余的時(shí)間學(xué)習(xí)一下這個(gè)創(chuàng)建framework的姿勢(shì),雖然這個(gè)過(guò)程非常非常簡(jiǎn)單,但是沒(méi)人指導(dǎo)你,自己摸索還是需要浪費(fèi)點(diǎn)時(shí)間的,走更著我的腳步一起去看看怎么打包自己的第一個(gè) framework.
先聲明本文都屬于操作部分,并且最基礎(chǔ),至于理論部分是參考文檔的,還有其他的理論都會(huì)在后面更新!
理論基礎(chǔ): 大神請(qǐng)?zhí)^(guò)
1. 庫(kù)
庫(kù)是源代碼經(jīng)過(guò)編譯弯囊,形成的二進(jìn)制代碼停忿,別人項(xiàng)目中使用我們的庫(kù)的時(shí)候潘悼,庫(kù)在參與編譯的時(shí)候,直接link就OK了,按照l(shuí)ink的方式贾陷,可以把庫(kù)分為靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)
2. 靜態(tài)庫(kù)
靜態(tài)庫(kù)在編譯的時(shí)候會(huì)被直接拷貝一份疮绷,復(fù)制到目標(biāo)程序里,這段代碼在目標(biāo)程序里就不會(huì)再改變了。
一般以.a 和 .framework為文件后綴名
這種做法是犧牲應(yīng)用“體量”來(lái)節(jié)省編譯時(shí)間唤锉。
3. 動(dòng)態(tài)庫(kù)
與靜態(tài)庫(kù)相反世囊,動(dòng)態(tài)庫(kù)在編譯時(shí)并不會(huì)被拷貝到目標(biāo)程序中,目標(biāo)程序中只會(huì)存儲(chǔ)指向動(dòng)態(tài)庫(kù)的引用窿祥。等到程序運(yùn)行時(shí)株憾,動(dòng)態(tài)庫(kù)才會(huì)被真正加載進(jìn)來(lái)。
動(dòng)態(tài)庫(kù)的優(yōu)點(diǎn)是壁肋,不需要拷貝到目標(biāo)程序中号胚,不會(huì)影響目標(biāo)程序的體積,而且同一份庫(kù)可以被多個(gè)程序使用(因?yàn)檫@個(gè)原因浸遗,動(dòng)態(tài)庫(kù)也被稱作共享庫(kù))猫胁。
同時(shí),編譯時(shí)才載入的特性跛锌,也可以讓我們隨時(shí)對(duì)庫(kù)進(jìn)行替換弃秆,而不需要重新編譯代碼。動(dòng)態(tài)庫(kù)帶來(lái)的問(wèn)題主要是髓帽,動(dòng)態(tài)載入會(huì)帶來(lái)一部分性能損失菠赚,使用動(dòng)態(tài)庫(kù)也會(huì)使得程序依賴于外部環(huán)境。如果環(huán)境缺少動(dòng)態(tài)庫(kù)或者庫(kù)的版本不正確郑藏,就會(huì)導(dǎo)致程序無(wú)法運(yùn)行
以.tbd(之前叫.dylib) 和 .framework 為文件后綴名
蘋果系統(tǒng)為我們提供了很多動(dòng)態(tài)鏈接庫(kù)衡查,我們可以在我們項(xiàng)目工程中查看一下
4. Framework
Framework 是一種打包方式,將庫(kù)的二進(jìn)制文件必盖,頭文件和有關(guān)的資源文件打包到一起拌牲,方便管理和分發(fā)。
Framework只是一種打包方式歌粥,其本身和靜態(tài)塌忽、動(dòng)態(tài)無(wú)關(guān)!
但Cocoa Touch Framework 的實(shí)際內(nèi)容為 Header + 動(dòng)態(tài)鏈接庫(kù) + 資源文件
5. 對(duì)Framework認(rèn)識(shí)的誤區(qū)
誤區(qū)①:.framework是動(dòng)態(tài)庫(kù)失驶,.a是靜態(tài)庫(kù)土居,前面已經(jīng)講過(guò),不再贅述
誤區(qū)②:有人說(shuō)“自定義的動(dòng)態(tài)庫(kù)蘋果審核不通過(guò)”嬉探,那我打的framework是不是通不過(guò)審核擦耀?
任何沒(méi)有時(shí)間前提的結(jié)論都是耍流氓!<撞觥埂奈!
在 iOS 8 / iOS6之前,iOS 平臺(tái)不支持使用動(dòng)態(tài) Framework定躏,開發(fā)者可以使用的 Framework 只有系統(tǒng)的framework账磺,這種限制可能是出于安全的考慮
換一個(gè)角度講芹敌,因?yàn)?iOS 應(yīng)用都是運(yùn)行在沙盒當(dāng)中,不同的程序之間不能共享代碼垮抗,同時(shí)動(dòng)態(tài)下載代碼又是被蘋果明令禁止的氏捞,沒(méi)辦法發(fā)揮出動(dòng)態(tài)庫(kù)的優(yōu)勢(shì),實(shí)際上動(dòng)態(tài)庫(kù)也就沒(méi)有存在的必要了
但是冒版,碼農(nóng)總是喜歡折騰的液茎,一方面骨子里面有一種“你不讓我做我偏要做的倔強(qiáng)”,另一方面用framework確實(shí)比用.a加頭文件的方式簡(jiǎn)單辞嗡,所以這一時(shí)期的開發(fā)者用了很多“奇淫技巧”來(lái)制作framework捆等,這就有了Fake Framework 和 Real Framework的區(qū)分
在 iOS 8 / iOS6后,iOS平臺(tái)添加了動(dòng)態(tài)庫(kù)的支持续室,同時(shí)栋烤, Xcode 6 也原生自帶了 Framework 支持,注意挺狰,前后兩個(gè)維度的不同是兩件事明郭,不要混淆。丰泊。薯定。
那么,為什么 iOS 8 要添加動(dòng)態(tài)庫(kù)的支持瞳购?
主要的理由大概就是 Extension 的出現(xiàn)话侄。Extension 和 App 是兩個(gè)分開的可執(zhí)行文件,同時(shí)需要共享代碼学赛,這種情況下動(dòng)態(tài)庫(kù)的支持就是必不可少的了满葛。但是這種動(dòng)態(tài) Framework 和系統(tǒng)的 UIKit.Framework 還是有很大區(qū)別;還有就是為了支持swift
雖然同樣是動(dòng)態(tài)框架罢屈,但是和系統(tǒng) framework 不同,app 中的使用的 Cocoa Touch Framework 在打包和提交 app 時(shí)會(huì)被放到 app bundle 中(App 和 Extension 的 Bundle 是共享的)篇亭,運(yùn)行在沙盒里缠捌,而不是系統(tǒng)中。也就是說(shuō)译蒂,不同的 app 就算使用了同樣的 framework曼月,但還是會(huì)有多份的框架被分別簽名,打包和加載柔昼,因此蘋果又把這種 Framework 稱為 Embedded Framework哑芹,也正是代碼簽名機(jī)制,通過(guò)AppStore發(fā)布的APP是無(wú)法通過(guò)替換服務(wù)端下發(fā)framework的方式來(lái)進(jìn)行熱更新捕透!
正文:
今天我們將采取“Aggregate+腳本”制作framework,配置選中手動(dòng)配置的方式,后面會(huì)考慮使用 xcconfig 來(lái)默認(rèn)設(shè)置
1, 配置環(huán)境,開源頭文件
打開xcode 創(chuàng)建framework項(xiàng)目
必須更改的兩個(gè)注意點(diǎn):
最后你還要更改SDK最低支持的版本,這個(gè)和項(xiàng)目更改系統(tǒng)支持的版本一樣,不再贅述
到此基本配置基本完成,也實(shí)現(xiàn)了項(xiàng)目的閉源
2, 添加自動(dòng)打包腳本環(huán)境,實(shí)現(xiàn)自動(dòng)打包
常見的錯(cuò)誤:
1, 沒(méi)有改成靜態(tài),不支持動(dòng)態(tài)
2, Defines Module 必須改成NO
3, 支持bitcode的frame操作
如果自己想要制作支持 Bitcode 的 Framework聪姿,
1, 工程中開啟 Enable Bitcode
2, Build Setting 中 Other Linker Flags 中加入 -fembed-bitcode碴萧。
3, Build Setting 中 Other C Flags 中加入 -fembed-bitcode。
同時(shí)支持模擬器真機(jī)的配置上文中會(huì)用到自動(dòng)打包的 shell 腳本,我使用的是公司其他大神寫的根據(jù)項(xiàng)目需求配置的腳本,這里就不開源了, 大家可以使用以下腳本,也可以實(shí)現(xiàn)自動(dòng)打包
#!/bin/sh
#要build的target名
TARGET_NAME=${PROJECT_NAME}
if [[ $1 ]]
then
TARGET_NAME=$1
fi
UNIVERSAL_OUTPUT_FOLDER="${SRCROOT}/${PROJECT_NAME}/"
#創(chuàng)建輸出目錄末购,并刪除之前的framework文件
mkdir -p "${UNIVERSAL_OUTPUT_FOLDER}"
rm -rf "${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework"
#分別編譯模擬器和真機(jī)的Framework
xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
#拷貝framework到univer目錄
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework" "${UNIVERSAL_OUTPUT_FOLDER}"
#合并framework破喻,輸出最終的framework到build目錄
lipo -create -output "${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${TARGET_NAME}.framework/${TARGET_NAME}"
#刪除編譯之后生成的無(wú)關(guān)的配置文件
dir_path="${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework/"
for file in ls $dir_path
do
if [[ ${file} =~ ".xcconfig" ]]
then
rm -f "${dir_path}/${file}"
fi
done
#判斷build文件夾是否存在,存在則刪除
if [ -d "${SRCROOT}/build" ]
then
rm -rf "${SRCROOT}/build"
fi
rm -rf "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator" "${BUILD_DIR}/${CONFIGURATION}-iphoneos"
#打開合并后的文件夾
open "${UNIVERSAL_OUTPUT_FOLDER}"