iOS動態(tài)庫闻妓、靜態(tài)庫及使用場景、方式

前面介紹過制作過程掠械,這里不講如何制作動態(tài)庫由缆、靜態(tài)庫注祖。

靜態(tài)庫和動態(tài)庫都是以二進制提供代碼復(fù)用的代碼庫。

  • 靜態(tài)庫常見的是 .a
  • 動態(tài)庫(共享庫)常見的是 Windows 下的 .dll均唉,Linux 下的 .so是晨,Mac 下的 .dylib/.tbd。

特別注意平時我們經(jīng)常說的Framework(in Apple) 是Cocoa/Cocoa Touch程序中使用的一種資源打包方式舔箭,可以將代碼文件罩缴、頭文件、資源文件层扶、說明文檔等集中在一起箫章,方便開發(fā)者使用。也就是說我們的 Framework其實是資源打包的方式镜会,和靜態(tài)庫動態(tài)庫的本質(zhì)是沒有什么關(guān)系炉抒。

靜態(tài)庫和動態(tài)庫的區(qū)別

首先來看什么是庫,庫(Library)說白了就是一段編譯好的二進制代碼稚叹,加上頭文件就可以供別人使用。我們在和別人合作的時候拿诸,一種情況是某些代碼需要給別人使用扒袖,但是我們不希望別人看到源碼,就需要以庫的形式進行封裝亩码,只暴露出頭文件季率。另外一種情況是,對于某些不會進行大的改動的代碼描沟,我們想減少編譯的時間飒泻,就可以把它打包成庫,因為庫是已經(jīng)編譯好的二進制了吏廉,編譯的時候只需要 Link 一下泞遗,不會浪費編譯時間。

  • 靜態(tài)庫:鏈接時會被完整的復(fù)制到可執(zhí)行文件中席覆,所以如果兩個程序都用了某個靜態(tài)庫史辙,那么每個二進制可執(zhí)行文件里面其實都含有這份靜態(tài)庫的代碼。
  • 動態(tài)庫: 鏈接時不復(fù)制佩伤,在程序啟動后用動態(tài)加載聊倔,然后再決議符號,所以理論上動態(tài)庫只用存在一份生巡,好多個程序都可以動態(tài)鏈接到這個動態(tài)庫上面耙蔑,達(dá)到了節(jié)省內(nèi)存(不是磁盤是內(nèi)存中只有一份動態(tài)庫),還有另外一個好處孤荣,由于動態(tài)庫并不綁定到可執(zhí)行程序上甸陌,所以我們想升級這個動態(tài)庫就很容易须揣,windows和linux上面一般插件和模塊機制都是這樣實現(xiàn)的。

動態(tài)庫和靜態(tài)庫都是由*.o目標(biāo)文件生成的邀层。

對比一下靜態(tài)和動態(tài)庫的優(yōu)缺點

庫類型 優(yōu)點 缺點
靜態(tài)庫 1. 目標(biāo)程序沒有外部依賴返敬,直接就可以運行。2. 效率教動態(tài)庫高寥院。
1. 會使用目標(biāo)程序的體積增大劲赠。
動態(tài)庫 1. 不需要拷貝到目標(biāo)程序中,不會影響目標(biāo)程序的體積秸谢。
2. 同一份庫可以被多個程序使用(因為這個原因凛澎,動態(tài)庫也被稱作共享庫)。
3. 編譯時才載入的特性估蹄,也可以讓我們隨時對庫進行替換塑煎,而不需要重新編譯代碼。實現(xiàn)動態(tài)更新
1. 動態(tài)載入會帶來一部分性能損失(可以忽略不計)
2. 動態(tài)庫也會使得程序依賴于外部環(huán)境臭蚁。如果環(huán)境缺少動態(tài)庫或者庫的版本不正確最铁,就會導(dǎo)致程序無法運行(Linux lib not found 錯誤)。

iOS的動態(tài)庫(被閹割的動態(tài)庫)

iOS平臺上規(guī)定不允許存在動態(tài)庫垮兑,并且所有的 IPA 都需要經(jīng)過Apple的私鑰加密后才能用冷尉,基本你用了動態(tài)庫也會因為簽名不對無法加載,(越獄和非 APP store 除外)系枪。于是就把開發(fā)者自己開發(fā)動態(tài)庫成為了天方夜譚雀哨。

iOS8之前因為 iOS 應(yīng)用都是運行在沙盒當(dāng)中,不同的程序之間不能共享代碼私爷,并且iOS是單進程的雾棺,也就是某一時刻只有一個進程在運行,那么你寫個共享庫衬浑,給誰共享呢捌浩。同時動態(tài)下載代碼又是被蘋果明令禁止的,沒辦法發(fā)揮出動態(tài)庫的優(yōu)勢工秩,綜上所以上動態(tài)庫也就沒有存在的必要了嘉栓。

但是后來iOS8之后,iOS有了App Extesion特性拓诸,而且Swift也誕生了侵佃。由于iOS主App需要和Extension共享代碼,Swift語言機制也需要動態(tài)庫奠支,于是蘋果后來提出了Embedded Framework馋辈,這種動態(tài)庫允許APP和APP Extension共享代碼,但是這份動態(tài)庫的生命被限定在一個APP進程內(nèi)倍谜。簡單點可以理解為被閹割的動態(tài)庫迈螟。

但是這種動態(tài)庫(Embedded Framework) 和系統(tǒng)的 UIKit.Framework 還是有很大區(qū)別叉抡,傳統(tǒng)的動態(tài)庫是給多個進程用的,而這里的動態(tài)庫(Embedded Framework)是給單個進程里面多個可執(zhí)行文件用的答毫。系統(tǒng)的 Framework 不需要拷貝到目標(biāo)程序中褥民,我們自己做出來的 動態(tài)庫(Embedded Framework) 哪怕是動態(tài)的,最后也還是要拷貝到 App 中(App 和 Extension 的 Bundle 是共享的)洗搂。所以蘋果沒有直接把這種Embedded Framework稱作動態(tài)庫而是叫Embedded Framework消返。

上面提到跟Swift也有原因,在Swift的項目中如果要在項目中使用外部的代碼耘拇,可選的方式只有兩種撵颊,一種是把代碼拷貝到工程中,另一種是用動態(tài) Framework惫叛。使用靜態(tài)庫是不支持的倡勇。這個問題的根本原因主要是 Swift 的運行庫沒有被包含在 iOS 系統(tǒng)中,而是會打包進 App 中(這也是造成 Swift App 體積大的原因)嘉涌,靜態(tài)庫會導(dǎo)致最終的目標(biāo)程序中包含重復(fù)的運行庫這是蘋果自家的解釋)妻熊。原文如下:

The current runtime doesn't ship with the OS, so static libs would lead to multiple runtimes in the final executable. A statically linked runtime would be much more difficult to patch for compatibility with newer OS or Swift.

iOS中的Embedded Framework可以理解為獨立的沒有main函數(shù)的可執(zhí)行文件。

基礎(chǔ)知識

前面提到的靜態(tài)庫可以簡單理解為一堆目標(biāo)文件(.o/.obj)的打包體(并非二進制文件)仑最,而動態(tài)庫可以簡單理解為 一個沒有main函數(shù)的可執(zhí)行文件固耘。

大學(xué)再講編譯原理的時候有兩個非常重要的過程,編譯和鏈接词身。編譯可以理解為將源代碼編譯為目標(biāo)文件,鏈接可以理解為將各種目標(biāo)文件上加一些第三方庫番枚、并且和系統(tǒng)庫鏈接起來為可執(zhí)行文件法严。因為某個目標(biāo)文件的符號(可以理解為變化、函數(shù))可能來至其他目標(biāo)文件葫笼,鏈接最為主要的就是決議符號的地址深啤。

編譯會生成目標(biāo)文件,目標(biāo)文件沒有經(jīng)過鏈接的過程路星,某些符號還沒有調(diào)整過溯街,Windows下的.obj文件,Linux下的.o文件洋丐,Unix的.out文件呈昔。

鏈接的過程可以簡單描述如下:
假如主程序main.c 使用了 fun.c 模塊的 foo函數(shù),那么main.c在編譯的過程友绝,對于調(diào)用foo函數(shù)的指令堤尾,對于指令的目標(biāo)地址暫時擱置;待到鏈接的時候迁客,由鏈接器來填寫foo函數(shù)的地址郭宝。

在決議符號的時候有如下規(guī)則:

  • 若符號來自靜態(tài)庫(本質(zhì)就是.o 的集合包)或 .o辞槐,將其納入鏈接產(chǎn)物,并確定符號地址粘室。常見的符號沖突就出現(xiàn)在這一步榄檬。
  • 若符號來自動態(tài)庫,打個標(biāo)記衔统,等啟動的時候再說---交給dyld去加載和鏈接符號鹿榜。也就是把鏈接的過程推遲到運行時再進行,上面講到的靜態(tài)庫符號沖突就可以推遲到運行時在解決缰冤,而具體怎么解決由系統(tǒng)去決定犬缨。如果這兩個符號表示的意思是一樣(比如函數(shù)符號沖突但是函數(shù)的實現(xiàn)是一樣的)的就沒有問題。

如果要深入了解一下相關(guān)知識棉浸,建議看一下《程序員自我修養(yǎng)》這本書怀薛,我也只懂皮毛。

靜態(tài)庫和動態(tài)庫依賴關(guān)系

  • 第一種靜態(tài)庫互相依賴迷郑,這種情況非常常見枝恋,制作靜態(tài)庫的時候只需要有被依賴的靜態(tài)庫頭文件在就能編譯出來。但是這就意味者你要收到告訴使用者你的依賴關(guān)系嗡害。
  • 第二種動態(tài)庫依賴動態(tài)庫焚碌,兩個動態(tài)庫是相互隔離的具有隔離性。在制作的靜態(tài)庫的時候需要被依賴動態(tài)庫參與鏈接霸妹,最終具體的符號決議交給dyld來做十电。
  • 第三種,靜態(tài)庫依賴動態(tài)庫叹螟,也很常見鹃骂,靜態(tài)庫制作的時候也需要動態(tài)庫參與鏈接,但是符號的決議交給dyld來做罢绽。
  • 第四種畏线,動態(tài)庫依賴靜態(tài)庫,這種情況就有點特殊良价。首先我們設(shè)想動態(tài)庫編譯的時候需要靜態(tài)庫參與編譯寝殴,但是靜態(tài)庫交由dyld來做符號決議,這和我們前面說的就矛盾了啊明垢。靜態(tài)庫本質(zhì)是一堆.o 的打包體蚣常,首先并不是二進制可執(zhí)行文件,再者你無法保證主程序把靜態(tài)庫參與鏈接共同生成二進制可執(zhí)行文件痊银。

對于第四種情況解決辦法如下:

目前的編譯器的解決辦法是史隆,首先我無法保證主程序是否包含靜態(tài)庫,再者靜態(tài)庫也無法被dyld加載曼验,那么我直接把你靜態(tài)庫的.o 偷過來泌射,共同組成一個新的二進制粘姜。也被稱做吸附性

如果有多個動態(tài)庫依賴這個靜態(tài)庫就會熔酷,每個動態(tài)庫為了保證自己的正確性會把靜態(tài)庫吸附進來孤紧。然后兩個庫包含了同樣的靜態(tài)庫,于是問題就出現(xiàn)了拒秘。

利用動態(tài)庫解決相關(guān)問題

有了上面的知識就可以解決一些平時遇到的疑難雜癥号显。

處理多個動態(tài)庫依賴一個靜態(tài)庫問題

通過前面我們知道可執(zhí)文件(主程序或者動態(tài)庫)在構(gòu)建的鏈接階段,遇到靜態(tài)庫躺酒,吸附進來押蚤;遇到動態(tài)庫,打標(biāo)記羹应,彼此保持獨立揽碘。

正因為動態(tài)庫是保持獨立的,那么我們可以自定義一個動態(tài)庫把依賴的靜態(tài)庫吸附進來园匹。對外整體呈現(xiàn)的是動態(tài)庫特性雳刺。其他的組件依賴我們自定義的動態(tài)庫,由于隔離性的存在裸违,不會出現(xiàn)問題掖桦。

這個思路在處理項目組件化的時候非常有用,尤其是在使用Swift的項目中供汛。

利用動態(tài)庫處理靜態(tài)庫與靜態(tài)庫的符號沖突問題

需要知道枪汪,在打包IPA的時候,最終靜態(tài)庫會被連接到最終的那個可執(zhí)行文件中怔昨。所以如果多個靜態(tài)庫擁有了相同的符號必定會產(chǎn)生符號沖突雀久。

前面講過可以把動態(tài)庫看成一個獨立的沒有main函數(shù)入口的可執(zhí)行文件,在iOS打包中直接copy到應(yīng)用程序.app目錄下的Frameworks目錄朱监。既然是可執(zhí)行文件那么內(nèi)部編譯連接過程已經(jīng)完成了,要處理的連接也只有在加載的時候由操作系統(tǒng)的dyld自動load + link原叮。

所以最終系統(tǒng)在加載動態(tài)庫的時候和靜態(tài)庫的符號根本沒有絲毫關(guān)系赫编,進而避免了鏈接時產(chǎn)生的符號沖突。

這一點在處理一些由于底層三方庫源碼不能手動修改(比如boringssl與openssl)的時候奋隶,非常有用擂送。

動態(tài)庫的動態(tài)裝載

目前iOS中動態(tài)更新方案有如下幾種:

  • HTML 5
  • lua(wax)hotpatch
  • react native
  • framework

使用 framework 的方式來更新可以不依賴第三方庫,使用原生的 OC/Swift 來開發(fā)唯欣,體驗更好嘹吨。由于 Apple 不希望開發(fā)者繞過 App Store 來更新 app,因此只有對于不需要上架的應(yīng)用境氢,才能以 framework 的方式實現(xiàn) app 的更新蟀拷。

使用framework實現(xiàn)動態(tài)更新常用用到的一些函數(shù)如下:

  • dlfcn.h中的的方法:用于處理動態(tài)庫的裝載碰纬、卸載。

    • dlopen打開動態(tài)鏈接庫问芬;
    • dlerror返回錯誤悦析;
    • dlsym獲取函數(shù)名或者變量名;
    • dlclose關(guān)閉動態(tài)庫此衅;
  • Objective-C的方法: 用于動態(tài)庫中對象的具體使用强戴。

    • NSClassFromString根據(jù)名字返回類;
    • NSSelectorFromString根據(jù)名字返回方法挡鞍;
    • performSelector執(zhí)行方法骑歹;

注意:沒有在在Linked的設(shè)置里面設(shè)置的動態(tài)庫,通過dlopen的形式來打開墨微。如果動態(tài)庫在Link Framwokrs and Libraries中設(shè)置了會在應(yīng)用啟動的時候就會被加載道媚。

在使用動態(tài)庫對象的時候必須使用NSClassFromString的方式,使用常見對象創(chuàng)建的方式是不可以的欢嘿。在使用dlopen打開動態(tài)庫的時候注意在build settings里面設(shè)置對應(yīng)的路徑衰琐,其中的@executable_path/表示可執(zhí)行文件所在路徑,即沙盒中的.app目錄炼蹦,注意不要漏掉最后的/羡宙。如果你將動態(tài)庫放到了沙盒中的其他目錄,只需要添加對應(yīng)路徑的依賴就可以了掐隐。

image.png

實例代碼的代碼如下:

打開動態(tài)庫

- (IBAction)onDlopenLoadAtPathAction1:(id)sender
{
    NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/Dylib.framework/Dylib",NSHomeDirectory()];
    [self dlopenLoadDylibWithPath:documentsPath];
}

- (void)dlopenLoadDylibWithPath:(NSString *)path
{
    libHandle = NULL;
    libHandle = dlopen([path cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW);
    if (libHandle == NULL) {
        char *error = dlerror();
        NSLog(@"dlopen error: %s", error);
    } else {
        NSLog(@"dlopen load framework success.");
    }
}

使用動態(tài)庫中的內(nèi)容

- (IBAction)onTriggerButtonAction:(id)sender
{
    Class rootClass = NSClassFromString(@"Person");
    if (rootClass) {
        id object = [[rootClass alloc] init];
        [(Person *)object run];
    }
}

擴展閱讀

編譯器的工作過程
編譯器
Mach-O 可執(zhí)行文件
Linux下的靜態(tài)庫狗热、動態(tài)庫和動態(tài)加載庫

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市虑省,隨后出現(xiàn)的幾起案子匿刮,更是在濱河造成了極大的恐慌,老刑警劉巖探颈,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熟丸,死亡現(xiàn)場離奇詭異,居然都是意外死亡伪节,警方通過查閱死者的電腦和手機光羞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怀大,“玉大人纱兑,你說我怎么就攤上這事』瑁” “怎么了潜慎?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我铐炫,道長垒手,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任驳遵,我火速辦了婚禮淫奔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘堤结。我一直安慰自己唆迁,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布竞穷。 她就那樣靜靜地躺著唐责,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瘾带。 梳的紋絲不亂的頭發(fā)上鼠哥,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音看政,去河邊找鬼朴恳。 笑死,一個胖子當(dāng)著我的面吹牛允蚣,可吹牛的內(nèi)容都是我干的于颖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼嚷兔,長吁一口氣:“原來是場噩夢啊……” “哼森渐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起冒晰,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤同衣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后壶运,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耐齐,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年蒋情,在試婚紗的時候發(fā)現(xiàn)自己被綠了埠况。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡恕出,死狀恐怖询枚,靈堂內(nèi)的尸體忽然破棺而出违帆,到底是詐尸還是另有隱情浙巫,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站的畴,受9級特大地震影響渊抄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丧裁,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一护桦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧煎娇,春花似錦二庵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哟绊,卻和暖如春因妙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背票髓。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工攀涵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人洽沟。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓以故,卻偏偏與公主長得像,于是被迫代替她去往敵國和親玲躯。 傳聞我的和親對象是個殘疾皇子据德,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容