前言
移動(dòng)端項(xiàng)目復(fù)雜到一定程度都會(huì)走上組件化的道路,組件一多就會(huì)出現(xiàn)聯(lián)編緩慢的問題。對(duì)于Objectiv-C語言的項(xiàng)目,想要加速編譯打包的速度,就需要將大量依賴的組件在打包的時(shí)候使用靜態(tài)庫依賴,以加快編譯鏈接速度。
iOS項(xiàng)目進(jìn)行組件化,一般會(huì)使用cocoapods包管理工具,二進(jìn)制庫在iOS項(xiàng)目中,指的是靜態(tài)庫與動(dòng)態(tài)庫,當(dāng)組件提供靜態(tài)庫或動(dòng)態(tài)庫的時(shí)候,可以加速項(xiàng)目編譯與構(gòu)建,因?yàn)殪o態(tài)庫與動(dòng)態(tài)庫本身就是已經(jīng)編譯好的庫文件,從而能達(dá)到加速的目的。
同時(shí)在開發(fā)時(shí),經(jīng)常會(huì)需要多人協(xié)作開發(fā),對(duì)于組件和一些不常改動(dòng)的文件,使用靜態(tài)庫可以有效避免因?yàn)檎`改而產(chǎn)生bug的問題。
一、總體目標(biāo)及方案選擇
1 目標(biāo)
通過對(duì)本文接下來的內(nèi)容的學(xué)習(xí),我們希望最終的目標(biāo)主要有:
1. 將組件制作為靜態(tài)庫。
2. 通過cocoapods進(jìn)行管理,實(shí)現(xiàn)源碼和靜態(tài)庫的切換。
2 相關(guān)方案的對(duì)比和選擇
2.1 靜態(tài)庫類型選擇
靜態(tài)庫.framework
與.a
的區(qū)別:
.a
:只把代碼文件打包編譯成二進(jìn)制。
.framework
:把代碼文件及其他資源,如圖片、音頻等文件,一起打包成二進(jìn)制。
選用何種二進(jìn)制類型,可以根據(jù)實(shí)際的項(xiàng)目情況進(jìn)行打包,在本文中,我們針對(duì)制作
.framework
進(jìn)行介紹。
2.2 打包方式選擇
靜態(tài)庫打包方式:
1.通過Xcode的打包方式,編譯打包。
2.使用Aggregate打包。
3.使用腳本直接打包。
4.使用第三方打包工具打包,如cocoapods-packager
考慮到簡單和易用,本文選用的是第二種Aggregate方式打包。
2.3 切換方案選擇
源碼和靜態(tài)庫切換方案:
1.在podspec
中使用if-else
條件去區(qū)分源碼和靜態(tài)庫,但是在進(jìn)行切換時(shí),每次都需要pod cache clean
,切換麻煩,也比較耗時(shí)。
2.使用Carthage
和cocoapods
結(jié)合的方式,由cocoapods
管理源碼,Carthage
管理靜態(tài)庫,學(xué)習(xí)成本較高。
3.使用subspec
實(shí)現(xiàn)源碼和靜態(tài)庫的切換,subspec
主要是在cocoapods
中給私有庫或第三方做目錄分層使用,在podfile
中寫入制定subspec
,可以只導(dǎo)入指定目錄下的文件。
綜合考慮下,本文選用第三種
subspec
的方式來實(shí)現(xiàn)切換,將源碼和靜態(tài)庫一起做成私有庫,分別放在兩個(gè)subspec
下。
二、制作靜態(tài)庫
1 創(chuàng)建framework
1.1 將要制作為靜態(tài)庫的組件克隆到本地
本文內(nèi)容是在已有私有庫的基礎(chǔ)上,針對(duì)私有庫中的組件來制作靜態(tài)庫,所以下面將直接從制作靜態(tài)庫開始介紹。
在項(xiàng)目的根目錄新建一個(gè)project或者在項(xiàng)目中新建一個(gè)target。
1.1 新建project
如果希望制作.a
文件,則選擇Static Library
。
1.2 新建target
新建target和project選擇其中一種方式即可。
2 設(shè)置framework
2.1 設(shè)置組件源碼
將組件的代碼文件引用到framework的target。
注意,這里不需要copy文件過去
2.2 其他設(shè)置
2.2.1 設(shè)置Build Settings
Architectures - 設(shè)置支持架構(gòu),這里
armv7、arm64(64位ARM處理器)
是真機(jī)架構(gòu),i386(32位模擬器)、x86_64(64位模擬器)
是模擬器架構(gòu)。
iOS Deployment Target - 設(shè)置可支持的最低版本。
Dead Code Stripping - 是否從framework中刪除未使用的代碼
Link With Standard Libraries - 是否鏈接蘋果標(biāo)準(zhǔn)庫
Mach-O Type - 這里的類型我們要選擇Static Library(靜態(tài))
Other Linker Flags(本文未使用) - 鏈接參數(shù),如果使用了category
,最好加上Objc、all_load
Other C Flags(本文未使用) - 額外的C語言鏈接參數(shù),如果需要支持bitcode
,需要加上-fembed -bitcode
Build Active Architecture Only - 是否只為當(dāng)前架構(gòu)編譯,NO則編譯所有架構(gòu)
2.2.2 設(shè)置Build Phases
將要暴露的頭文件移到public中
3 設(shè)置Aggregate
3.1 新建Aggregate
3.2 設(shè)置打包腳本
3.3 編寫腳本內(nèi)容
將以下內(nèi)容復(fù)制進(jìn)Run Script中,修改第三行中的
TARGET_NAME
改為自己創(chuàng)建的framework名字,這段腳本會(huì)自動(dòng)合并真機(jī)和模擬器的二進(jìn)制文件。
#!/bin/sh
#要build的target名
TARGET_NAME='TenUIKitFramework'
#${PROJECT_NAME}
if [[ $1 ]]
then
TARGET_NAME=$1
fi
UNIVERSAL_OUTPUT_FOLDER="${SRCROOT}/${PROJECT_NAME}_Products/"
#創(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}"
#刪除編譯之后生成的無關(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}"
4 打包
以上內(nèi)容設(shè)置完之后,就可以打包了,選擇Aggregate進(jìn)行編譯,編譯完成后會(huì)自動(dòng)打開編譯好的framework存儲(chǔ)的文件夾。
5 靜態(tài)庫中使用cocoapods管理依賴的第三方
在之前建立的project中使用cocoapods導(dǎo)入第三方(pod init、修改Podfile、pod install),然后在要使用的地方導(dǎo)入頭文件,在framework中編譯一下,成功后則可以繼續(xù)第4步的打包流程。
三、使用cocoapods管理
1 移動(dòng)靜態(tài)庫到合適的位置
首先我們將剛才獲得的靜態(tài)庫文件夾移動(dòng)到根目錄,或者也可以不移動(dòng),我是為了等會(huì)設(shè)置路徑方便。
2 設(shè)置靜態(tài)庫與源碼的切換
下面我們通過編寫
podspec
文件來進(jìn)行設(shè)置,其中source
代表源碼,framework
代表靜態(tài)庫,pod導(dǎo)入時(shí)用于進(jìn)行區(qū)分,默認(rèn)使用framework。
Pod::Spec.new do |s|
s.name = 'TenUIKit'
s.version = '0.1.1'
s.summary = 'A short description of TenUIKit.'
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
s.homepage = '你的項(xiàng)目地址'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Ten' => '賬號(hào)' }
s.source = { :git => 'git地址', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.ios.deployment_target = '8.0'
s.default_subspec = 'framework'
s.subspec 'source' do |ss|
ss.source_files = 'TenUIKit/Classes/**/*'
end
s.subspec 'framework' do |ss|
ss.ios.vendored_framework = 'TenUIKit_Products/*.framework'
end
end
3 收尾
這之后就可以push代碼到你的私有庫,在項(xiàng)目中使用時(shí),
podfile
中按如下寫法
#使用默認(rèn)framework
pod 'TenUIKit'
#使用源碼
pod 'TenUIKit', :subspec => ['source']
四、資源文件bundle的制作
framework只能包含頭文件和代碼,而不能包含圖片和
storyboard
等資源文件,這是就需要?jiǎng)?chuàng)建bundle來保存這些資源,以在其他工程中使用。
創(chuàng)建bundle文件有兩種方式,通過xcode創(chuàng)建或者自己手動(dòng)創(chuàng)建,下面我們將分別針對(duì)這兩種方式來進(jìn)行介紹。
1 通過xcode方式創(chuàng)建
1.1 創(chuàng)建target
首先在上文的工程中再添加一個(gè)target,選擇
macOS
中的bundle
。
1.2 設(shè)置Build Settings
Base SDK - 默認(rèn)是macOS用的,這里修改為iOS
Skip Install - 資源包是否需要安裝,這里我們選擇No不安裝
Installation Directory - 安裝路徑,不需要安裝所以這我們刪除
1.3 設(shè)置Build Phases
將資源文件放進(jìn)bundle中
1.4 使用bundle
將bundle文件添加到
framework
所在的工程后,在framework
中的Build Phases中添加bundle
1.5 項(xiàng)目中使用
使用時(shí)framework找到自己的bundle可以參考如下代碼:
[NSBundle bundleWithPath:[[NSBundle bundleForClass:self.class] pathForResource:@"TenPremissionsBundle" ofType:@"bundle" inDirectory:@"TenPremissionsFramework.framework"]];
2 自己手動(dòng)創(chuàng)建
這個(gè)方法也很簡單,只需要?jiǎng)?chuàng)建一個(gè)新文件夾,重命名為"xxx.bundle",即可創(chuàng)建出一個(gè)bundle文件,右鍵顯示包內(nèi)容,即可向里面添加資源文件,使用的步驟與使用xcode創(chuàng)建出的bundle文件的使用方法一樣。
3 framework與bundle的組合使用
3.1 項(xiàng)目中直接使用
上面講述的使用方式中,我們將framework和bundle打包在一起,這樣的好處主要是bundle隨時(shí)和framework綁定,避免了遺漏的情況。但相應(yīng)的也因?yàn)樾枰獙ramework拷貝到APP包中,增大了空間占用,完整的framework包也都暴露在APP包中。
所以更建議使用
framework
和bundle
分離的方式,bundle
文件不加入到framework
工程下,framework
中的Copy Bundle Resources
下也不要添加bundle
,分別將framework
和bundle
導(dǎo)入APP中,這樣APP中的Copy Bundle Resources
只需要添加bundle
文件,而不需要添加framework
。
最后記得將在framework
中查找bundle
的代碼改成如下示例:
[NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"TenPermissionsBundle" ofType:@"bundle"]];
3.2 使用cocoapods管理
整體思路與上面相同,只需要將bundle
文件與framework
分開路徑存儲(chǔ),一起push到組件庫中。