原文地址 ,此簡書只做備份,強烈推薦原文,畢竟格式比簡書好看,還清晰
回憶之前
上篇文章中我們已經完美的解決了 使用
swift第三方庫
,使用混編的組件
,使用use_framework!
,但是會帶來別的問題。
果然是生命不息,折騰不止啊。
不建議在組件化的項目中使用Swift來寫業務。
Q: C++/C 靜態庫依賴問題
A: 回想下我們在做C或者C++開發的時候。如果一個靜態庫依賴另外一個靜態庫(A依賴B)。那么被依賴庫B升級的時候A用重新編譯嗎? 不一定,如果是一些方法的新增,維護,不一定會讓A重復編譯;但是如果修改了B里面的數據結構,A里面又用到了這些數據結構,那么很大可能性我們就要重新編譯A了。
Q: Objective-C 靜態庫依賴問題
A: 回想下我們在iOS中出現上述的依賴問題,貌似也沒有見到要重新編譯A的情景。主要是Objc2.0引入了 non-fragile特性,同時OC是嚴重依賴于Runtime的,只要接口兼容,就算你修改了B中的數據結構,一般也是不需要重新編譯A的。如果你不明白non-fragile 請看文后的參考鏈接
Q: Swift中庫依賴問題
A: 由于Swift不和OC一樣,所有的OC方法都是通過Runtime動態調度的。Swift對于方法是存在靜態調度和動態調度2種的。所以Swift的庫依賴極易引起二進制兼容性問題。更多關于Swift庫二進制接口(ABI)兼容性問題,請參考文后鏈接。
Q: 為什么不建議在組件化的項目中使用Swift或者和OC混編來寫業務?
A: 首先在組件化初期的時候,我們能做到的一般是基礎庫抽離,業務組件分離這些。但是一般來說我們這時候的殼工程,接入這些分離的組件的時候都是使用源碼接入,這時候問題暫時顯現不出來。
第二步。當我們的組件化的腳步越走越遠的時候,我們出于多方面的考慮可能有以下需求。
- 開發時重復編譯是痛點。我們可能更希望提供的是二進制版本,節省下大量的編譯耗時;
- 我們可能要做權限管理。有時候一個公司業務和人員規模都非常龐大。我們基礎庫設計到跨業務,跨APP使用。我們希望不同團隊有不同基礎組件的讀寫權限。那么我們更可能偏向提供二進制庫加文檔的形式。
綜上: 由于使用Swift開發ABI不兼容問題更易出現。在組件化的項目中,不建議使用Swift或者混編。
動態庫過多問題
上面說到的問題(麻煩)其實是帶給開發者的麻煩,但是動態庫多了會給用戶帶來麻煩(APP啟動耗時)。用了混編的項目我們在Podfile
里面勢必要寫use_framework!
,上篇文章中我們也說到用了這個指令。CocoaPods
會幫我們把所有的庫全部編譯為動態庫。這些動態庫是在APP啟動時做去加載的。我們在組件化的時候,自己的業務組件馬上接近上百個??梢灶A想到以后隨著組件化的越來越深入,這些庫會越來越多。這個時間可能會達到1s的量級。對于用戶 這是不可接受的。關于動態庫過多導致的啟動慢的問題請參考文后的參考鏈接。
結合公司目前的情況的解決方案
我們公司目前的情況: Swift第三方庫個別,混編組件個別。既然都是個別的,我們總不能因為這些個別的特殊case讓APP原本的1個二進制文件變成 1個二進制文件+上百個動態庫framework。這肯定是不合理的。
解決辦法
- 不使用Swift,包括第三方庫和混編組件
- 部分組件(含有Swift)動態庫化,其他部分仍舊整合進app的二進制中
首先來看辦法1直觀感覺是不合適。首先很多公司的項目在做組件化的時候項目已經達到一定程度(沒有一定規模也沒必要做組件化),這就意味著大部分APP是有歷史包袱的。首先重寫這些已有的組件或者功能肯定是有風險的,在公司業務多。用戶量大的情況下,影響面會更大,雖然這樣是一勞永逸的,但是同時風險是更大的。我們在做組件化的工作中,改善大家開發的痛點,提高開發效率才是主要目標。至于重構甚至重寫則是業務方的重心。
第二種辦法就是做到部分組件動態庫化。
我們來回憶下靜態庫的特點。靜態庫和主工程鏈接的時候會把庫里面的代碼復制到可執行文件中。對于這部分符號在APP啟動時會省去load,rebase ,binding的時間。那么在iOS平臺中嵌入式動態庫的特點是不把庫里面的代碼復制到可執行文件中,而是單獨復制到APP里面的frameworks路徑下。所以通常來說動態庫節省內存。在iOS平臺上做不到。靜態庫的缺點是會讓APP安裝包增大。那么我們自己做的嵌入式動態庫也會有這個問題。并且還會導致APP啟動變慢。那豈不是優點變成了缺點~~.
以上討論只在正常項目且上架到APP Store渠道,越獄開發和企業版證書發布不做討論
組件化部分動態庫實戰
上篇文章中我們知道 只要你的組件庫中使用到了Swift。以源碼
的方式提供給殼工程使用的時候一定要加上use_framework!
, 那么就變成前文說到了上百個動態庫了。 那么我們如果不以源碼的形式引入呢。對于這些含有Swift的下層組件是無依賴的。我們直接將其編譯為動態庫提供二進制。那么我們在主工程使用的時候就不需要加入use_framework!
.
#use_frameworks!
source 'https://github.com/ValiantCat/LJWXSDK'
source 'https://github.com/CocoaPods/Specs.git' #官方倉庫的地址
target 'LJA_Example' do
pod 'LJA', :path => '../'
pod 'LJB', :path => '../'
pod 'LJCharts'
end
那么我們拉下來的項目結構是這樣的
但是我們運行發現
libswiftCore.dylib
無法加載。出現這個原因呢是因為Xcode不知道你使用了Swift代碼,所以并沒有把Swift的運行時環境(也就是swift運行的動態庫)復制進APP目錄。那么解決辦法其實很簡單。我們在殼工程新建一個swift空文件即可。
以下是主工程是否有swift文件APP目錄下動態庫的對比
到這里其實這篇文章就好了。剩下的其他組件就繼續使用靜態庫即可。我們可以愉快的玩耍了。
未解決的問題
前面說的問題文中已經解決。但是我覺得有一點不爽。那就是我還需要手動得在殼工程添加空的Swift文件。那么我們能不能這一步也自動化呢。
首先我建了一個空工程,往里面添加了一個空的Swift文件。然后diff了一下兩次的project文件。
以下是diff結果
我們知道podspec和Podfile其實都是ruby代碼。Xcode如何知道我們是否有swift其實也是通過工程的配置來知曉的。那么我們其實可以在Podfile去寫ruby代碼修改工程文件。這樣的話使用方就不需要疑惑為什么要加個空的swift文件了。
以下是代碼
# Pod設置 =================================
def update_config (config)
config.build_settings['CLANG_ENABLE_MODULES'] = 'YES'
config.build_settings['SWIFT_VERSION'] = '3.0'
# config.target_attributes["LastSwiftMigration"] = "0830"
if config.name == "Debug" then
config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Onone'
end
# elsif config.name == "Release" then
# config.build_settings['CLANG_ENABLE_MODULES'] = 'YES'
# config.build_settings['SWIFT_VERSION'] = '3.0'
# end
end
post_install do |installer|
projects = [
"SwiftConfig"
]
projects.each do |proj|
path = "%s.xcodeproj" % [proj]
single_project = Xcodeproj::Project.open(path)
single_project.targets.each do |target|
target.build_configurations.each do |config|
print path, ' ', target.name, ' ', config.name
puts ""
update_config config
end
target.attributes.methods.each do |xx|
puts xx
end
end
single_project.save
end
end
不過加入這段代碼后發現。我雖然確實成功的修改了工程文件。但是發現Xcode依舊沒有把Swift運行時的庫給我復制進APP里面。
所以這還算是一個不完美的地方。后續有結果的話更新此文。