一、前言
在介紹動態(tài)庫手動加載方式之前,我們簡單了解下動態(tài)庫,又名共享庫在iOS中是個特殊的存在,除了系統(tǒng)庫以外,在大部分使用場景下(除了App Extension可以共享)其實并不能達(dá)到共享的目的。在iOS開發(fā)中動態(tài)庫主要有以下用途:
解決蘋果審核iOS8
__Text字段60M限制,將獨立的代碼封裝到動態(tài)庫,進(jìn)而減小可執(zhí)行文件代碼段的大小。制作第三方庫,因為動態(tài)庫沒有像靜態(tài)庫之間的符號沖突問題(Xcode會有沖突日志,不影響運(yùn)行),很多時候第三方庫往往會以動態(tài)庫的形式存在。
不同于靜態(tài)庫會被一起鏈接到Mach-O文件中,動態(tài)庫是獨立于主程序存在的。我們使用動態(tài)庫時一般是直接拖到工程中,設(shè)置下Embed,使用起來非常方便。這些動態(tài)庫是在App啟動的時候通過dyld(動態(tài)鏈接器)根據(jù)依賴關(guān)系遞歸的加載到內(nèi)存中,這樣的方式稱為動態(tài)庫自動加載。但是如果動態(tài)庫數(shù)量多了,會大大的拖慢應(yīng)用的啟動速度,因為dyld在rebase和binding階段比較耗時。
那么,對于動態(tài)庫使用比較多的項目怎么去優(yōu)化App啟動的耗時呢?其實除了自動加載方式,還有一種是手動加載(也稱為懶加載),我們可以將一些不常用的動態(tài)庫模塊使用手動加載方式。
二、使用
動態(tài)庫手動加載有兩種方式可以實現(xiàn):
dlopen;
NSBundle load/loadAndReturnError;
蘋果在審核條款中明確禁止使用dlopen(感謝@
iOSLover的分享,和審核團(tuán)隊確認(rèn):加簽過的動態(tài)庫可以使用dlopen。本人未做驗證,僅供參考。),我們重點看下NSBundle load/loadAndReturnError的方式,load的方式底層也是使用dlopen實現(xiàn),只是增加了驗簽,而簽名是在App打包的時候完成。如果從其他途徑(如網(wǎng)絡(luò)下載)獲取的動態(tài)庫是無法完成驗簽的。
手動方式加載方式如下:
在Build Phases中點擊"+"-"New Copy Files Phase",新增
Copy Files選項,如果有動態(tài)庫Strip的腳本,需要將Copy Files拖到前面,保證在打包時可以執(zhí)行去除i386/x86_64指令集;-
修改Copy Files 中的
Destination選項為Frameworks,這樣手動加載的動態(tài)庫也會和其他動態(tài)庫拷貝到同一個目錄,點擊"+"-"Add Others..."添加需要手動加載的動態(tài)庫;
Xcode截圖
現(xiàn)在,我們可以使用了(因為是動態(tài)加載的,調(diào)用方式也只能是動態(tài)調(diào)用):
NSString *path = [[NSBundle mainBundle] pathForResource:@"MyLib" ofType:@"framework" inDirectory:@"Frameworks"];
NSError *err = nil;
NSBundle *bundle = [NSBundle bundleWithPath:path];
if ([bundle loadAndReturnError:&err]) {
//加載成功,方法調(diào)用
Class c = NSClassFromString(@"MyClass");
[c performSelector:@selector(printLog)];
}
else {
//加載失敗
}
二、擴(kuò)展
上面的使用方式比較適合沒有依賴的動態(tài)庫。那么,我們能不能將一個業(yè)務(wù)模塊轉(zhuǎn)成動態(tài)庫呢?業(yè)務(wù)模塊往往會依賴各種各樣的庫,如網(wǎng)絡(luò)庫,埋點庫,UI組件庫等等...。而這些庫可能是靜態(tài)庫,也可能是動態(tài)庫。先看下靜態(tài)庫/動態(tài)庫的打包時的依賴的特性:
靜態(tài)庫依賴靜態(tài)庫,只引用,相互獨立;
靜態(tài)庫依賴動態(tài)庫,只引用,相互獨立;
動態(tài)庫依賴動態(tài)庫,只引用,相互獨立;
動態(tài)庫依賴靜態(tài)庫,鏈接到一起;
從上面的特性可以看出,動態(tài)庫如果依賴靜態(tài)庫會“合并”靜態(tài)庫。這樣被依賴的靜態(tài)庫在項目中有多份“拷貝”,這會大大增加包大小。制作動態(tài)庫時可以這樣做:
- 在動態(tài)庫工程制作動態(tài)庫的時候,刪除
Link Binary With Libraries中依賴的靜態(tài)庫,保留工程目錄下的引用不要刪除;
Link Binary With Libraries
- "other linker Flags" 中添加
-undefined dynamic_lookup;
other linker Flags
這樣打包出來的動態(tài)庫就不會包含靜態(tài)庫了...
因為動態(tài)庫引用了主執(zhí)行文件(靜態(tài)庫最后會被鏈接到主執(zhí)行文件)的符號,所以主工程的配置也需要跟著修改:
"Build Settings"-"Strip Style" 修改為Non-Global Symbols,將外部引用的符號保留,當(dāng)然這會略微增加包大小。

三、加載成功率 & 性能
從線上監(jiān)控數(shù)據(jù)來看未發(fā)現(xiàn)加載失敗的情況,成功率可達(dá)100%。加載耗時跟設(shè)備性能、特別是動態(tài)庫符號(類名,協(xié)議,方法名等)數(shù)量有關(guān)。97% 以上幾乎在用戶無感知的情況下加載完成(毫秒級)。手動加載的效率比自動加載效率低,請勿在app啟動過程中使用。
四、結(jié)束語
以上是動態(tài)庫手動加載的使用方式,隨著越來越多的App放棄iOS8,使用動態(tài)庫來解決__Text 大小限制的需求變得越來越少。但可以作為App啟動優(yōu)化中動態(tài)庫部分的優(yōu)化方案(成本低,效果好)。對于組件化(如CocoaPods)構(gòu)建的工程,上述的配置方案會有不同,但是原理一樣。


