前言
移動端項目復(fù)雜到一定程度都會走上組件化的道路,組件一多就會出現(xiàn)聯(lián)編緩慢的問題。對于Objectiv-C語言的項目,想要加速編譯打包的速度,就需要將大量依賴的組件在打包的時候使用靜態(tài)庫依賴,以加快編譯鏈接速度。
iOS項目進行組件化,一般會使用cocoapods包管理工具,二進制庫在iOS項目中,指的是靜態(tài)庫與動態(tài)庫,當組件提供靜態(tài)庫或動態(tài)庫的時候,可以加速項目編譯與構(gòu)建,因為靜態(tài)庫與動態(tài)庫本身就是已經(jīng)編譯好的庫文件,從而能達到加速的目的。
同時在開發(fā)時,經(jīng)常會需要多人協(xié)作開發(fā),對于組件和一些不常改動的文件,使用靜態(tài)庫可以有效避免因為誤改而產(chǎn)生bug的問題。
一、總體目標及方案選擇
1 目標
通過對本文接下來的內(nèi)容的學(xué)習(xí),我們希望最終的目標主要有:
1. 將組件制作為靜態(tài)庫。
2. 通過cocoapods進行管理,實現(xiàn)源碼和靜態(tài)庫的切換。
2 相關(guān)方案的對比和選擇
2.1 靜態(tài)庫類型選擇
靜態(tài)庫.framework與.a的區(qū)別:
.a:只把代碼文件打包編譯成二進制。
.framework:把代碼文件及其他資源,如圖片、音頻等文件,一起打包成二進制。
選用何種二進制類型,可以根據(jù)實際的項目情況進行打包,在本文中,我們針對制作
.framework進行介紹。
2.2 打包方式選擇
靜態(tài)庫打包方式:
1.通過Xcode的打包方式,編譯打包。
2.使用Aggregate打包。
3.使用腳本直接打包。
4.使用第三方打包工具打包,如cocoapods-packager
考慮到簡單和易用,本文選用的是第二種Aggregate方式打包。
2.3 切換方案選擇
源碼和靜態(tài)庫切換方案:
1.在podspec中使用if-else條件去區(qū)分源碼和靜態(tài)庫,但是在進行切換時,每次都需要pod cache clean,切換麻煩,也比較耗時。
2.使用Carthage和cocoapods結(jié)合的方式,由cocoapods管理源碼,Carthage管理靜態(tài)庫,學(xué)習(xí)成本較高。
3.使用subspec實現(xiàn)源碼和靜態(tài)庫的切換,subspec主要是在cocoapods中給私有庫或第三方做目錄分層使用,在podfile中寫入制定subspec,可以只導(dǎo)入指定目錄下的文件。
綜合考慮下,本文選用第三種
subspec的方式來實現(xiàn)切換,將源碼和靜態(tài)庫一起做成私有庫,分別放在兩個subspec下。
二、制作靜態(tài)庫
1 創(chuàng)建framework
1.1 將要制作為靜態(tài)庫的組件克隆到本地
本文內(nèi)容是在已有私有庫的基礎(chǔ)上,針對私有庫中的組件來制作靜態(tài)庫,所以下面將直接從制作靜態(tài)庫開始介紹。
在項目的根目錄新建一個project或者在項目中新建一個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處理器)是真機架構(gòu),i386(32位模擬器)、x86_64(64位模擬器)是模擬器架構(gòu)。

iOS Deployment Target - 設(shè)置可支持的最低版本。

Dead Code Stripping - 是否從framework中刪除未使用的代碼
Link With Standard Libraries - 是否鏈接蘋果標準庫
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 - 是否只為當前架構(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ù)制進Run Script中,修改第三行中的
TARGET_NAME改為自己創(chuàng)建的framework名字,這段腳本會自動合并真機和模擬器的二進制文件。
#!/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"
#分別編譯模擬器和真機的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進行編譯,編譯完成后會自動打開編譯好的framework存儲的文件夾。

5 靜態(tài)庫中使用cocoapods管理依賴的第三方
在之前建立的project中使用cocoapods導(dǎo)入第三方(pod init、修改Podfile、pod install),然后在要使用的地方導(dǎo)入頭文件,在framework中編譯一下,成功后則可以繼續(xù)第4步的打包流程。
三、使用cocoapods管理
1 移動靜態(tài)庫到合適的位置
首先我們將剛才獲得的靜態(tài)庫文件夾移動到根目錄,或者也可以不移動,我是為了等會設(shè)置路徑方便。
2 設(shè)置靜態(tài)庫與源碼的切換
下面我們通過編寫
podspec文件來進行設(shè)置,其中source代表源碼,framework代表靜態(tài)庫,pod導(dǎo)入時用于進行區(qū)分,默認使用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 = '你的項目地址'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Ten' => '賬號' }
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代碼到你的私有庫,在項目中使用時,
podfile中按如下寫法
#使用默認framework
pod 'TenUIKit'
#使用源碼
pod 'TenUIKit', :subspec => ['source']
四、資源文件bundle的制作
framework只能包含頭文件和代碼,而不能包含圖片和
storyboard等資源文件,這是就需要創(chuàng)建bundle來保存這些資源,以在其他工程中使用。
創(chuàng)建bundle文件有兩種方式,通過xcode創(chuàng)建或者自己手動創(chuàng)建,下面我們將分別針對這兩種方式來進行介紹。
1 通過xcode方式創(chuàng)建
1.1 創(chuàng)建target
首先在上文的工程中再添加一個target,選擇
macOS中的bundle。

1.2 設(shè)置Build Settings
Base SDK - 默認是macOS用的,這里修改為iOS

Skip Install - 資源包是否需要安裝,這里我們選擇No不安裝
Installation Directory - 安裝路徑,不需要安裝所以這我們刪除

1.3 設(shè)置Build Phases
將資源文件放進bundle中

1.4 使用bundle
將bundle文件添加到
framework所在的工程后,在framework中的Build Phases中添加bundle

1.5 項目中使用
使用時framework找到自己的bundle可以參考如下代碼:
[NSBundle bundleWithPath:[[NSBundle bundleForClass:self.class] pathForResource:@"TenPremissionsBundle" ofType:@"bundle" inDirectory:@"TenPremissionsFramework.framework"]];
2 自己手動創(chuàng)建
這個方法也很簡單,只需要創(chuàng)建一個新文件夾,重命名為"xxx.bundle",即可創(chuàng)建出一個bundle文件,右鍵顯示包內(nèi)容,即可向里面添加資源文件,使用的步驟與使用xcode創(chuàng)建出的bundle文件的使用方法一樣。


3 framework與bundle的組合使用
3.1 項目中直接使用
上面講述的使用方式中,我們將framework和bundle打包在一起,這樣的好處主要是bundle隨時和framework綁定,避免了遺漏的情況。但相應(yīng)的也因為需要將framework拷貝到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分開路徑存儲,一起push到組件庫中。
