背景: 項目採用 Target-Action + CocoaPods 進行組件化, 去年由 Objective-C 轉向 Swift, 全部新的組件所有使用 Swift 編寫, 主幹項目由 Objective-C 和 Swift 混編. 在體驗到面向值和協議編程的便利的同時, 也深深被 Swift 編譯速度所困擾. 在對 Swift 編譯速度優化後, 效果仍是不夠理想, 因此爲了提升主幹項目打包提測的速度, 決定將組件二進制化.html
關於 Cocoapods 私有庫的建立就不說了, 官方文檔裏也都有用法.git
因爲項目中的業務組件中也會擁有資源文件, 且以前 Swift 不支持靜態庫, 因此採用動態庫 framework 的方式.github
更新: Xcode9 beta 4 和 CocoaPods 1.5 已經支持 Swift 靜態庫.shell
其實就是使用 CocoaPods 對源文件和 .framework 進行管理, 使用 pod install
和 pod update
命令來拉取和切換資源文件.編程
這裏主要記錄一下二進制組件的建立和使用 及 .podspec 配置.swift
其中涉及到的工具備:vim
使用 Xcode 進行編譯時, 默認生成的 .framework 文件會存放在 Xcode 文件夾下xcode
咱們能夠將編譯後的 .framework 移動到文件夾內進行版本管理, 可是若是每次手動拖動到項目文件夾中, 難免有些累贅. 且模擬器和真機也須要不一樣 architecture, 因此咱們能夠編寫一個 build.sh 來自動生成及合併 exec 可執行文件, 並將生成的 .framework 保存到項目目錄內. 其中主要的命令是:ruby
# 生成 iphoneos 和 iphonesimulator 兩種可執行文件
$ xcodebuild -workspace ${WORKSPACE_NAME}".xcworkspace" -configuration "${CONFIG}" -scheme ${SCHEME_NAME} SYMROOT=$(PWD)/build -sdk iphoneos clean build
$ xcodebuild -workspace ${WORKSPACE_NAME}".xcworkspace" -configuration "${CONFIG}" -scheme ${SCHEME_NAME} SYMROOT=$(PWD)/build -sdk iphonesimulator clean build
# 合併爲一個可執行文件
$ lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${DES_DIR}/${FMK_NAME}"
複製代碼
build.sh 接受兩個參數, 分別是項目名稱和構建配置, 將腳本置於 .workspace 同級目錄下, 如如下命令將輸出 Debug 配置的 BAPurchase.framework:bash
./build.sh BAPurchase Debug
複製代碼
Objective-C framework 中, 使用 lipo 合成 iphoneos 和 iphonesimulator 可執行文件後, .framework 便可正常工做, 不過在合成 Swift framework 後, 使用 .framework 會出現錯誤:
'SomeClass' is unavailable: cannot find Swift declaration for this class
這是由於 Swift framework 內包含有 .swiftmodule 文件, 其定義了 framework 所支持的 architecture, 因此對於 Swift framework, 咱們除了將 .exec 文件合併外, 還須要將 .framework/Module/.swiftmodule 文件夾內的全部描述文件移動到一塊兒:
cp -R "${SIMULATOR_DIR}/Modules/${FMK_NAME}.swiftmodule/." "${DES_DIR}/Modules/${FMK_NAME}.swiftmodule/"
複製代碼
腳本完成後, 接下來使用 Xcode 添加一個 Aggregate Target
用來單獨調用腳本, 同時實現工程參數的自動填充.
File->New->Target->cross-platform->Aggregate
, 新建一個 Aggregate Target
:
並在 Build Phases->New Run Script Phase
內添加新的 script, 並輸入如下命令用於調用 build.sh:
${SRCROOT}/build.sh ${PROJECT_NAME} ${CONFIGURATION}
複製代碼
上面的指令等價於下面兩條命令, 省卻了配置信息的輸入 :
$ cd BAPurchaseDirectory
$ ./build.sh BAPurchase Debug #Release, Debug, InHouse, AdHoc
複製代碼
使用 Aggregate Target
的一大好處是不用每次都 cd 到目錄去運行腳本, 只須要**切換到 Aggregate Target
, 確認 Edit Scheme -> Run->Build Configuration
, 執行 build **便可.
.framework 由 shell 生成後位於工程目錄下, 和源文件一塊兒存放, 咱們還須要另外配置 .podspec 實現選擇性導入文件, 介紹兩種方式:
if ENV[ 'f' ]
s.vendored_framework = 'BAPurchase/Frameworks/BAPurchase.framework'
else
s.source_files = 'BAPurchase/Classes/**/*'
s.resource_bundles = {
'Resources' => 'BAPurchase/Assets/*/**'
}
end
複製代碼
導入時使用如下指令切換到 .framework:
$ pod install
$ f=1 pod update BAPurchase
複製代碼
但這種方式有兩個問題:
pod update BAPurchase
的時候會將全部其餘的 pod 也更新, 即若是有多個私有庫都配置了 framework 和源文件導入, 則咱們對其中一個使用 pod update
會致使兩個都切換到 framework 或源文件.pod install
時添加參數, 只能 pod install
源文件, 以後再次執行 f=1 pod update BAPurchase
指令切換到動態庫.s.subspec 'Framework' do |sf|
sf.s.vendored_framework = 'BAPurchase/Frameworks/BAPurchase.framework'
end
s.subspec 'Core' do |sc|
sc.source_files = 'BAPurchase/Classes/**/*'
sc.resource_bundles = {
'Resources' => 'BAPurchase/Assets/*/**'
}
end
s.default_subspecs = 'Framework'
複製代碼
以上配置定義了兩個導入方式: 源碼和 .framework, 默認導入方式爲 .framework. 若是須要進行源碼調試, 則能夠修改 podfile 爲 pod BAPurchase/Core
, 而後執行如下命令:
$ vim podfile
# pod 'BAPurchase/Core'
$ pod update BAPurchase --no-repo-update
複製代碼
subspec 的好處顯而易見:
podfile
內直接指定導入方式爲源碼或 .framework.pod update
時作到互不干擾, 只更新指定的私有庫.與條件判斷相比, 也有缺點:
業務組件內頗有可能會包含有環境變量, 最典型的是 Objective-C 中使用預處理宏 #if DEBUG
來獲取測試域名或生產域名, 可是二進制組件是已經編譯完成的, 有可能不能匹配主項目的環境配置. 能夠用兩種方式解決:
使用 CocoaPods 的 subspec 能夠完成該配置, 此時組件提供三個subspec:
平時默認導入 Debug framework, 在須要調試時切換到源碼, 在線上測試和提審時使用 Release framework. 這樣的優缺點很明顯:
Debug 和 Release 除了編譯器優化的區別外, 影響最大的就是使用 Preprocessor Macros(Objective-C) 和 Active Compilation Conditions(Swift). 咱們能夠把環境變量的肯定延遲到連接時: 在組件內定義一個環境變量, 全部的域名根據環境變量對應返回, 這個變量由主項目傳遞, 詳情參考示例.
我更傾向於使用組件環境變量, 避免在組件中使用 Preprocessor Macros(Objective-C) 和 Active Compilation Conditions(Swift).
另外, 對於項目中大量依賴的三方庫, 也能夠將他們製做成私有 framework, 減小編譯時間, 在須要源碼調試時, 切換成源代碼便可. 也能夠經過 CocoaPods 插件的方式, 在 pod install
或 pod update
後, 將三方代碼直接編譯成 framework 進行集成.
Error: Unknown class _SomeModuleSomeCell in Interface Builder file:
這是因爲組件中的 Xib 有對應的 class, xib 加載後會去將 outlet 賦值到對應類實例, 而類和 xib 不在同一 bundle 內形成錯誤. 因此須要在 xib 的 Identity Inspector->Custom Class->Module
指定類所屬模塊.
Error: 'ASwiftFrameworkClass' is unavailable: cannot find Swift declaration for this class
對 Swift framework 進行多 architecture 合併時, 除了 exec 可執行文件外, 還須要將 .framework/Modules 文件夾內的描述文件一併合併, 不然編譯時會提示錯誤.
Error: Module 'BAPurchase' not found
在 Objective-C 源項目中導入 Swift framework 後, 會出現此錯誤, 須要在 Objective-C Target -> Build Settings
中, 設置 alwaysEmbedSwiftStandardLibraries = YES