移動端項目復雜到一定程度都會走上組件化的道路,組件一多就會出現(xiàn)聯(lián)編緩慢的問題。Android項目可以通過gradle,依賴源碼生成jar包,提高編譯速度。對于Objective-C語言的項目,想要加速編譯打包的速度,就需要將大量依賴的組件在打包的時候都使用靜態(tài)庫或者動態(tài)庫依賴,加快編譯鏈接速度,以滿足持續(xù)集成或者是快速部署的要求。
iOS的項目進行組件化,往往會使用cocoapods包管理工具,該方案以此為基礎。二進制庫在iOS項目中,指的是靜態(tài)庫與動態(tài)庫,當組件提供靜態(tài)庫或動態(tài)庫的時候,可以加速項目編譯與構建,因為靜態(tài)庫與動態(tài)庫本身就是已經(jīng)編譯好的庫文件,從而能達到加速的目的。
目標
- pod組件同時提供源碼與二進制庫
- 項目進行調試的時候,pod組件庫切換為源碼模式,方便開發(fā)進行源碼斷點調試
- 項目進行集成與構建的時候,pod組件庫切換為二進制模式,加快編譯構建速度
- 開發(fā)組件的時候不因為源碼與二進制的切換方案而喪失依賴其他組件的能力
方案說明與步驟
對于某一個組件來說,組件的pod庫應該包含源碼和靜態(tài)庫或動態(tài)庫兩種文件,這樣才能夠在開發(fā)的過程中使用源碼進行編譯調試,在編譯構建的時候使用靜態(tài)庫或動態(tài)庫。關鍵問題在于如何切換,cocoapods的pod庫是通過podfile文件指明依賴的庫與對應版本,當使用pod install
的時候,cocoapods會通過podfile文件,到cocoapods的中央倉庫中找到該庫對應的podspec文件,再通過podspec文件中的信息來構建pod庫。
一個pod庫的podspec文件如下:
Pod::Spec.new do |s|
s.name = 'HBAuthentication'
s.version = '0.1.6-beta5'
s.summary = '基礎認證組件'
s.description = <<-DESC
iOS認證組件,相關文檔請訪問內部wiki:
http://***.***.com/member/Auth
DESC
s.homepage = 'http://***.***.com/Hbec_IOS_common/HBAuthentication'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Neo' => '***@***.com' }
s.source = { :git => 'git@***.com:Hbec_IOS_common/HBAuthentication.git', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.ios.deployment_target = '7.0'
s.resource_bundles = {
'HBAuthentication' => ['HBAuthentication/Assets/*.{png,cer,json,der,p12}']
}
s.source_files = 'HBAuthentication/Classes/**'
end
cocoapods從中央倉庫拉取到對應版本的podsepc文件以后,通過s.source獲得對應版本tag的代碼git版本庫,而后通過s.source_files、s.resource_bundles指明該庫的源碼文件與資源文件對應的路徑,從而最終進行源碼依賴構建編譯。如果該庫的為二進制庫,則需要通過 s.public_header_files、s.ios.vendored_libraries來指明該庫的二進制庫的頭文件、library文件的路徑。所以,該方案要求一個pod庫的工程文件中不僅僅要包含源代碼文件,還要包含將源代碼編譯成靜態(tài)庫或者動態(tài)庫的二進制文件,切換二進制庫與源碼的時機應該在 pod install
的時候,而表明是構建源碼還是二進制庫,則需要通過install的時候,修改podspec文件中的s.source_files、s.public_header_files、s.ios.vendored_bibraries屬性,來切換該pod庫包含的內容。因為podspec文件本身為ruby文件,我們可以利用ENV對象,來獲取命令行中執(zhí)行pod install
時候傳入的環(huán)境變量,例如可以在podspec文件中這樣寫:
if ENV['SOURCECODE']
s.source_files = 'HBAuthentication/Classes/**'
else
s.source_files = 'Example/HBAuthenticationBinary/Products/Binary-universal/include/**'
s.public_header_files = '**/*.h'
s.ios.vendored_libraries = '**/**.a'
end
當在命令行中傳入環(huán)境變量參數(shù)的時候 SOURCECODE=1 pod install
的時候,則podspec文件中if 語句通過ENV對象來獲取SOURCECODE參數(shù)來表明不同的文件包含屬性,從而能夠切換該pod庫源碼或者二進制庫。
通過cocoapods的環(huán)境變量來控制組件庫spec文件的配置信息,后面會詳細說到。通過以上的分析,那么該方案大體上分為這幾個步驟:
- 創(chuàng)建pod項目
- 創(chuàng)建對應的二進制庫target
- 生成與源碼對應的二進制文件
- 設置pod庫的podspec文件,切換源碼和二進制庫的配置
- 發(fā)布含有源碼和二進制庫的pod庫
創(chuàng)建pod項目和創(chuàng)建對應的二進制庫target
通過 pod lib create HBAuthentication
創(chuàng)建出的pod庫項目,目錄大概如下:
.
├── Example
│ ├── HBAuthentication
│ ├── HBAuthentication.xcodeproj
│ ├── HBAuthentication.xcworkspace
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Pods
│ └── Tests
├── HBAuthentication
│ ├── Assets
│ └── Classes
├── HBAuthentication.podspec
├── LICENSE
├── README.md
├── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj
└── com.touker.hbauthentication.HBAuthentication.docset
└── Contents
項目的源碼包含在Class文件中,如下:
├── Assets
│ └── content.json
└── Classes
├── HBAuthAPI.h
├── HBAuthAPI.m
├── HBAuthBridge.h
├── HBAuthBridge.m
├── HBAuthInfo.h
├── HBAuthInfo.m
├── HBAuthObject.h
├── HBAuthObject.m
├── HBAuthStoreManager.h
├── HBAuthStoreManager.m
├── HBAuthUtil.h
├── HBAuthUtil.m
一個源碼的pod庫項目大概就是這樣,現(xiàn)在需要創(chuàng)建對應的二進制庫,以靜態(tài)庫為例,在項目中添加對應的靜態(tài)庫target:file->New->Target->iOS->Framework & Library->Cocoa Touch Static Library
target命名為:HBAuthenticationBinary,創(chuàng)建后項目目錄如下:
.
├── Example
│ ├── HBAuthentication
│ ├── HBAuthentication.xcodeproj
│ ├── HBAuthentication.xcworkspace
| ├── HBAuthenticationBinary
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Pods
│ └── Tests
├── HBAuthentication
│ ├── Assets
│ └── Classes
├── HBAuthentication.podspec
├── LICENSE
├── README.md
├── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj
└── com.touker.hbauthentication.HBAuthentication.docset
└── Contents
緊接著將class文件夾下的源文件添加到HBAuthenticationBinary的target目錄下,添加的時候選擇不復制,添加文件索引。
此時項目文件如下:
緊接著設置Build Phases中的Compile Source與想要對外暴露的Headers,如下:
靜態(tài)庫的源碼設置已經(jīng)完成。
生成與源碼對應的二進制文件
靜態(tài)庫需要考慮支持的目標架構,arm架構或者是x86架構,前者用于真機后者用于模擬器調試,一般在不考慮靜態(tài)庫大小的情況下,可以將幾種架構打成一個靜態(tài)庫,方便使用。可以通過xcodebuild命令行工具進行打包和架構合并,要生成.a文件,為了支持真機和模擬器的版本構建,通過xcodebuild與lipo工具,來生成支持x86、arm64、armv7的靜態(tài)庫,將此操作寫成腳本,通過Aggregate Target來執(zhí)行,腳本如下:
set -e
set +u
### Avoid recursively calling this script.
if [[ $UF_MASTER_SCRIPT_RUNNING ]]
then
exit 0
fi
set -u
export UF_MASTER_SCRIPT_RUNNING=1
### Constants.
# RESOURCE_BUNDLE="HBAuthenticationBinary"
# 靜態(tài)庫target對應的scheme名稱
SCHEMENAME="HBAuthenticationBinary"
# .a與頭文件生成的目錄,在項目中的HBAuthenticationBinary目錄下的Products目錄中
BASEBUILDDIR=$PWD/${SCHEMENAME}/Products
rm -fr "${BASEBUILDDIR}"
mkdir "${BASEBUILDDIR}"
# 支持全架構的二進制文件目錄
UNIVERSAL_OUTPUTFOLDER=${BASEBUILDDIR}/Binary-universal
# 支持真機的二進制文件目錄
IPHONE_DEVICE_BUILD_DIR=${BASEBUILDDIR}/Binary-iphoneos
# 支持模擬器的二進制文件目錄
IPHONE_SIMULATOR_BUILD_DIR=${BASEBUILDDIR}/Binary-iphonesimulator
### Functions
## List files in the specified directory, storing to the specified array.
#
# @param $1 The path to list
# @param $2 The name of the array to fill
#
##
list_files ()
{
filelist=$(ls "$1")
while read line
do
eval "$2[\${#$2[*]}]=\"\$line\""
done <<< "$filelist"
}
### Take build target.
if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]
then
SF_SDK_PLATFORM=${BASH_REMATCH[1]} # "iphoneos" or "iphonesimulator".
else
echo "Could not find platform name from SDK_NAME: $SDK_NAME"
exit 1
fi
### Build simulator platform. (i386, x86_64)
# echo "========== Build Simulator Platform =========="
# echo "===== Build Simulator Platform: i386 ====="
# xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_SIMULATOR_BUILD_DIR}/i386" SYMROOT="${SYMROOT}" ARCHS='i386' VALID_ARCHS='i386' $ACTION
echo "===== 構建x86_64架構 ====="
xcodebuild -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${SCHEMENAME}" -configuration "${CONFIGURATION}" -sdk iphonesimulator CONFIGURATION_BUILD_DIR="${IPHONE_SIMULATOR_BUILD_DIR}/x86_64" ARCHS='x86_64' VALID_ARCHS='x86_64' $ACTION
# Build device platform. (armv7, arm64)
echo "========== Build Device Platform =========="
echo "===== Build Device Platform: armv7 ====="
xcodebuild -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${SCHEMENAME}" -configuration "${CONFIGURATION}" -sdk iphoneos CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/armv7" ARCHS='armv7 armv7s' VALID_ARCHS='armv7 armv7s' $ACTION
echo "===== Build Device Platform: arm64 ====="
xcodebuild -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${SCHEMENAME}" -configuration "${CONFIGURATION}" -sdk iphoneos CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/arm64" ARCHS='arm64' VALID_ARCHS='arm64' $ACTION
### Build universal platform.
echo "========== Build Universal Platform =========="
## Copy the framework structure to the universal folder (clean it first).
rm -rf "${UNIVERSAL_OUTPUTFOLDER}"
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
## Copy the last product files of xcodebuild command.
cp -R "${IPHONE_DEVICE_BUILD_DIR}/arm64/lib${SCHEMENAME}.a" "${UNIVERSAL_OUTPUTFOLDER}/lib${SCHEMENAME}.a"
### Smash them together to combine all architectures.
lipo -create "${IPHONE_SIMULATOR_BUILD_DIR}/x86_64/lib${SCHEMENAME}.a" "${IPHONE_DEVICE_BUILD_DIR}/armv7/lib${SCHEMENAME}.a" "${IPHONE_DEVICE_BUILD_DIR}/arm64/lib${SCHEMENAME}.a" -output "${UNIVERSAL_OUTPUTFOLDER}/lib${SCHEMENAME}.a"
echo "========== Create Standard Structure =========="
cp -r "${IPHONE_DEVICE_BUILD_DIR}/arm64/usr/local/include/" "${UNIVERSAL_OUTPUTFOLDER}/include/"
# mkdir -p "${UNIVERSAL_OUTPUTFOLDER}/lib/"
# cp "${UNIVERSAL_OUTPUTFOLDER}/lib${SCHEMENAME}.a" "${UNIVERSAL_OUTPUTFOLDER}/lib/lib${SCHEMENAME}.a"
將該腳本保存問build.sh,在Aggregate Target中設置Build Phases的Run Script,如下:
將靜態(tài)庫文件和頭文件輸出到 HBAuthenticationBinary/Products 目錄。
運行Aggregate Target以后,HBAuthenticationBinary目錄結構如下:
├── HBAuthenticationBinary
│ └── Products
│ ├── Binary-iphoneos
│ │ ├── arm64
│ │ │ ├── libHBAuthenticationBinary.a
│ │ │ ├── libPods-HBAuthenticationBinary.a
│ │ │ └── usr
│ │ │ └── local
│ │ │ └── include
│ │ │ ├── HBAuthAPI.h
│ │ │ ├── HBAuthBridge.h
│ │ │ ├── HBAuthInfo.h
│ │ │ ├── HBAuthObject.h
│ │ │ └── HBAuthStoreManager.h
│ │ └── armv7
│ │ ├── libHBAuthenticationBinary.a
│ │ ├── libPods-HBAuthenticationBinary.a
│ │ └── usr
│ │ └── local
│ │ └── include
│ │ ├── HBAuthAPI.h
│ │ ├── HBAuthBridge.h
│ │ ├── HBAuthInfo.h
│ │ ├── HBAuthObject.h
│ │ └── HBAuthStoreManager.h
│ ├── Binary-iphonesimulator
│ │ └── x86_64
│ │ ├── libHBAuthenticationBinary.a
│ │ ├── libPods-HBAuthenticationBinary.a
│ │ └── usr
│ │ └── local
│ │ └── include
│ │ ├── HBAuthAPI.h
│ │ ├── HBAuthBridge.h
│ │ ├── HBAuthInfo.h
│ │ ├── HBAuthObject.h
│ │ └── HBAuthStoreManager.h
│ └── Binary-universal
│ ├── include
│ │ ├── HBAuthAPI.h
│ │ ├── HBAuthBridge.h
│ │ ├── HBAuthInfo.h
│ │ ├── HBAuthObject.h
│ │ └── HBAuthStoreManager.h
│ └── libHBAuthenticationBinary.a
其實Binary-universal就是最終的靜態(tài)庫的文件,其他的Binary-iphonesimulator與Binary-iphoneos目錄下的文件都不需要包含到pod的git版本庫中。
設置pod庫的podspec文件,切換源碼和二進制庫的配置
接下來設置podspec文件,內容如下:
Pod::Spec.new do |s|
s.name = 'HBAuthentication'
s.version = '0.1.6-beta5'
s.summary = '基礎認證組件'
s.description = <<-DESC
iOS認證組件,相關文檔請訪問內部wiki:
http://***.***.com/member/Auth
DESC
s.homepage = 'http://***.***.com/Hbec_IOS_common/HBAuthentication'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Neo' => '***@***.com' }
s.source = { :git => 'git@***.com:Hbec_IOS_common/HBAuthentication.git', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.ios.deployment_target = '7.0'
s.resource_bundles = {
'HBAuthentication' => ['HBAuthentication/Assets/*.{png,cer,json,der,p12}']
}
s.source_files = 'HBAuthentication/Classes/**'
end
if ENV['SOURCECODE']
puts '-----------'
puts 'HBAuthentication Source Code'
else
puts '+++++++++++'
puts 'HBAuthentication Binary'
s.source_files = 'Example/HBAuthenticationBinary/Products/Binary-universal/include/**'
s.public_header_files = 'Example/HBAuthenticationBinary/Products/Binary-universal/include/*.h'
s.ios.vendored_libraries = 'Example/HBAuthenticationBinary/Products/Binary-universal/libHBAuthenticationBinary.a'
end
s.dependency 'CocoaLumberjack'
s.dependency 'HBWebBridge'
end
發(fā)布含有源碼和二進制庫的pod庫
此時在Example中測試該pod庫,在podfile中添加該庫依賴:
pod 'HBAuthentication', :path => '../'
然后使用 pod install
此時,會發(fā)現(xiàn)Pods的Development Pods目錄下的HBAuthentication為下圖:
說明為靜態(tài)庫依賴。
在Example項目中使用 SOURCECODE=1 pod install
以后,則切換到源碼模式。
分別運行測試,都沒有問題,可以將工程文件提交到git倉庫,注意前面生成的其他架構的文件目錄可以刪除,不提交到git版本庫中,因為是二進制文件,git對二進制文件不能做到增量更新,隨著版本增加,git版本庫會越來越大,所以最好精簡靜態(tài)庫的大小,最后在自己的私有cocoapods倉庫中進行pod庫的發(fā)布。私有cocoapods庫搭建參考官方文檔