iOS應(yīng)用程序瘦身的靜態(tài)庫解決方案

為什么要給程序瘦身跟磨?

隨著應(yīng)用程序的功能越來越多,實(shí)現(xiàn)越來越復(fù)雜岭辣,第三方庫的引入吱晒,UI體驗(yàn)的優(yōu)化等眾多因素程序中的代碼量成倍的增長甸饱,從而導(dǎo)致應(yīng)用程序包的體積越來越大沦童。當(dāng)程序體積變大后不僅會(huì)出現(xiàn)編譯流程變慢,而且還會(huì)出現(xiàn)運(yùn)行性能問題叹话,會(huì)增加應(yīng)用下載時(shí)長和消耗用戶的移動(dòng)網(wǎng)絡(luò)流量等等偷遗。因此在這些眾多的問題下需要對(duì)應(yīng)用進(jìn)行瘦身處理。

一個(gè)應(yīng)用程序由眾多資源文件和可執(zhí)行程序文件組成驼壶,資源文件的優(yōu)化不在本文探討范圍氏豌。本文主要討論對(duì)可執(zhí)行程序代碼瘦身的方法。

對(duì)可執(zhí)行程序代碼瘦身主要就是想辦法讓程序中不會(huì)被調(diào)用的源代碼不參與編譯或鏈接热凹。我們可以通過一些源代碼分析工具來查找哪些函數(shù)或者類方法沒有被調(diào)用并從代碼中刪除掉來解決編譯鏈接前的瘦身問題泵喘。這些分析工具也不在本文的討論范圍內(nèi)。應(yīng)用程序在編譯時(shí)會(huì)對(duì)工程中的所有代碼都執(zhí)行編譯處理并生成目標(biāo)文件般妙。而在鏈接階段則會(huì)根據(jù)程序代碼中對(duì)符號(hào)的引用關(guān)系來將所有相關(guān)的目標(biāo)文件鏈接為一個(gè)大的可執(zhí)行程序文件纪铺,并且在鏈接階段鏈接器會(huì)優(yōu)化掉所有沒被調(diào)用的C/C++函數(shù)代碼,但是對(duì)于OC類中的沒有調(diào)用的方法則不會(huì)被優(yōu)化掉碟渺。所以為了對(duì)可執(zhí)行程序在編譯鏈接階段進(jìn)行瘦身處理就需要了解源代碼的編譯鏈接規(guī)則鲜锚。這也是本文所要介紹的針對(duì)工程通過靜態(tài)庫的形式進(jìn)行編譯和鏈接的方式來減少可執(zhí)行程序代碼的尺寸。您可以從文章:《深入iOS系統(tǒng)底層之靜態(tài)庫介紹》中詳細(xì)的了解到靜態(tài)庫的編譯鏈接過程,以及相關(guān)的技術(shù)細(xì)節(jié)芜繁。

一個(gè)瘦身的例子旺隙!

為了驗(yàn)證和具體的實(shí)踐,我在github上建立了一個(gè)項(xiàng)目:YSAppSizeTest骏令。您可以從這個(gè)項(xiàng)目中看到如何對(duì)工程進(jìn)行構(gòu)建以實(shí)現(xiàn)程序的瘦身處理蔬捷。

在示例項(xiàng)目中同一個(gè)Workspace中分別建立ThinApp和FatApp兩個(gè)工程,這兩個(gè)工程實(shí)現(xiàn)的功能是一樣榔袋。在整個(gè)應(yīng)用程序中分別定義了CA抠刺、CB、CC摘昌、CD速妖、CE一共5個(gè)OC類,定義了一個(gè)UIView(Test)分類聪黎,還有定義了兩個(gè)C函數(shù):libFoo1和libFoo1罕容。

整個(gè)應(yīng)用程序中只使用了CA和CC兩個(gè)OC類,以及調(diào)用了UIView(Test)分類方法稿饰,以及調(diào)用了libFoo1函數(shù)锦秒,并且同時(shí)都采用導(dǎo)入靜態(tài)庫的形式。因?yàn)檫@兩個(gè)工程對(duì)文件的定義和分布策略不同使得兩個(gè)應(yīng)用程序的最終可執(zhí)行代碼的尺寸是不相同的喉镰。

FatApp中的文件定義和分布策略

  1. FatApp工程依賴并導(dǎo)入了FatAppLib靜態(tài)庫工程旅择。
  2. CA,CB兩個(gè)類都定義在主程序工程中。
  3. CC,CD,CE三個(gè)類侣姆,以及UIView(Test)分類生真,還有l(wèi)ibFoo1,libFoo2兩個(gè)函數(shù)都定義在FatAppLib靜態(tài)庫工程中。
  4. CC,CD兩個(gè)類定義在同一個(gè)文件中捺宗,CE類則定義在單獨(dú)的文件中柱蟀。
  5. FatApp工程的Other Linker Flags中設(shè)置了 -ObjC選項(xiàng)。

ThinApp中的文件定義和分布策略

  1. ThinApp工程依賴并導(dǎo)入了ThinAppLib靜態(tài)庫工程蚜厉。
  2. 主程序工程就是一個(gè)殼工程长已。
  3. CA,CB,CC,CD,CE5個(gè)類,以及UIView(Test)分類昼牛,還有l(wèi)ibFoo1,libFoo2兩個(gè)函數(shù)都定義在ThinAppLib靜態(tài)庫工程中术瓮。
  4. 上述的5個(gè)類都分別定義在不同的文件中。
  5. ThinApp工程的Other Linker Flags中沒有設(shè)置-ObjC選項(xiàng)贰健。

上述兩個(gè)工程的程序被Archive出來后胞四,F(xiàn)atApp可執(zhí)行程序的尺寸是367KB,而ThinApp可執(zhí)行程序的尺寸是334KB霎烙。通過一些工具比如Mach-O View或者 IDA可以看出:FatApp中5個(gè)OC類的代碼以及l(fā)ibFoo1函數(shù)還有UIView(Test)分類的代碼都被鏈接進(jìn)可執(zhí)行程序中撬讽;而ThinApp中則只有CA,CC兩個(gè)類以及l(fā)ibFoo1函數(shù)還有UIView(Test)分類的代碼被鏈接進(jìn)可執(zhí)行程序中蕊连。在ThinApp中雖然沒有使用-Objc鏈接選項(xiàng),但是靜態(tài)庫中的分類也被鏈接進(jìn)可執(zhí)行程序中游昼。

應(yīng)用程序工程構(gòu)建規(guī)則

根據(jù)對(duì)項(xiàng)目中的文件定義和引用策略以及相關(guān)的理論基礎(chǔ)我們可以按照如下的規(guī)則來構(gòu)建您的應(yīng)用程序:

  1. 盡量將所有代碼都移植到靜態(tài)庫中甘苍,而主程序則保留為一個(gè)殼程序。具體操作方法是建立一個(gè)Workspace烘豌,然后主程序工程就只有默認(rèn)創(chuàng)建工程時(shí)的代碼载庭,所有新加入的代碼都建立并存放到靜態(tài)庫工程中去,然后通過工程依賴來引入這些靜態(tài)庫工程廊佩,或者借助一些工程化工具比如Cocoapods來實(shí)現(xiàn)這種拆分和引用處理囚聚。主程序工程中只保留AppDelegate的代碼,其他代碼都一致到靜態(tài)庫中标锄。然后在AppDelegate中的相關(guān)代碼處調(diào)用靜態(tài)庫中定義的業(yè)務(wù)代碼顽铸。

  2. 按業(yè)務(wù)組件對(duì)工程進(jìn)行解耦每個(gè)組件是一個(gè)靜態(tài)庫工程。靜態(tài)庫中的每一個(gè)文件中最好只有一個(gè)類的實(shí)現(xiàn)料皇,并且類的分類實(shí)現(xiàn)最好和類實(shí)現(xiàn)編寫在同一個(gè)文件中谓松,相同功能的代碼以及可能都會(huì)被調(diào)用的代碼盡量存放在一個(gè)文件中。

  3. 不要在主程序工程中使用-ObjC和-all_load兩個(gè)選項(xiàng)而改為用-force_load 來單獨(dú)指定要執(zhí)行加載的靜態(tài)庫践剂。-ObjC和-all_load選項(xiàng)會(huì)把主程序工程以及所依賴的所有靜態(tài)庫中的工程中的全部代碼都鏈接到可執(zhí)行程序中而不管代碼是否有被調(diào)用過或者使用過鬼譬。而force_load則只會(huì)將指定的靜態(tài)庫中的所有代碼鏈接到可執(zhí)行程序中,當(dāng)然force_load如果沒有必要也盡量不要使用逊脯。

  4. 盡量減少在靜態(tài)庫中定義OC類的分類方法优质,如果一定要定義分類方法則可以將分類方法定義在和類定義相同的文件中,或者將分類方法定義在一個(gè)一定會(huì)被調(diào)用和引用的實(shí)現(xiàn)文件中军洼。因?yàn)楦鶕?jù)鏈接規(guī)則靜態(tài)庫中的分類是不會(huì)被鏈接進(jìn)可執(zhí)行程序中的巩螃,除非使用了上述的三個(gè)鏈接選項(xiàng)。如果將分類代碼單獨(dú)的定義在一個(gè)文件中的話則可以通過在分類的頭文件中定義一個(gè)內(nèi)聯(lián)函數(shù)歉眷,內(nèi)聯(lián)函數(shù)調(diào)用分類實(shí)現(xiàn)文件中的一個(gè)dumy函數(shù),這樣只要這個(gè)分類的頭文件被include或者import就會(huì)把整個(gè)分類的實(shí)現(xiàn)鏈接到可執(zhí)行程序中去牺六。一般情況下我們?cè)陟o態(tài)庫中建立分類那就表明一定會(huì)被某個(gè)文件引用這個(gè)分類颤枪,從而實(shí)現(xiàn)整個(gè)文件的鏈接處理汗捡。在分類中定義的這兩個(gè)函數(shù)則因?yàn)闆]有被任何地方調(diào)用,因此會(huì)在鏈接優(yōu)化中將這兩個(gè)函數(shù)給優(yōu)化掉畏纲。這樣就使得即使我們不用-ObjC選項(xiàng)也能將靜態(tài)庫中的分類鏈接到可執(zhí)行程序中去扇住。最后需要注意的是在每個(gè)分類中定義的這兩個(gè)函數(shù)名最好能夠唯一這樣就不會(huì)出現(xiàn)符號(hào)重名沖突的問題了。

//分類文件的頭文件UIView+XXX.h
@interface UIView (XXX)

//分類中定義的方法

@end

/*
  通過在分類的頭文件中定義一個(gè)內(nèi)聯(lián)函數(shù)盗胀,內(nèi)聯(lián)函數(shù)調(diào)用分類實(shí)現(xiàn)文件中的一個(gè)dumy函數(shù),這樣只要這個(gè)分類的頭文件被include或者import就會(huì)把
  整個(gè)分類的實(shí)現(xiàn)鏈接到可執(zhí)行程序中去艘蹋。一般情況下我們?cè)陟o態(tài)庫中建立分類那就表明一定會(huì)被某個(gè)文件引用這個(gè)分類,從而實(shí)現(xiàn)整個(gè)文件的鏈接處理票灰。
  而在分類中定義的這兩個(gè)函數(shù)則因?yàn)闆]有被任何地方調(diào)用女阀,因此會(huì)在鏈接優(yōu)化中將這兩個(gè)函數(shù)給優(yōu)化掉宅荤。這樣就使得即使我們不用-ObjC選項(xiàng)也能
  將靜態(tài)庫中的分類鏈接到可執(zhí)行程序中去。最后需要注意的是在每個(gè)分類中定義的這兩個(gè)函數(shù)名最好能夠唯一這樣就不會(huì)出現(xiàn)符號(hào)重名沖突的問題了浸策。
 */
extern void _cat_UIView_XXX_Impl(void);
inline void _cat_UIView_XXX_Decl(void){_cat_UIView_XXX_Impl();}


------------------------------------------------------------
//分類文件的實(shí)現(xiàn)文件UIView+XXX.m
#import "UIView+XXX.h"

@implementation UIView (XXX)

//分類的實(shí)現(xiàn)代碼

@end

void _cat_UIView_XXX_Impl(void){}


---------------------------------------------------------------
//最后把這個(gè)分類頭文件放入到某個(gè)對(duì)外暴露的頭文件中冯键,比如本例中將分類代碼放入到了ThinAppLib.h文件中
//ThinAppLib.h

#import "UIView+XXX.h"
//其他頭文件

  1. 除了可以通過-force_load來加載指定靜態(tài)庫中的所有代碼外。我們還可以在構(gòu)建靜態(tài)庫時(shí)庸汗,在靜態(tài)庫的工程的Build Settings中將Perform Single-Object Prelink 中的開關(guān)選項(xiàng)打開惫确。當(dāng)這個(gè)開關(guān)打開時(shí),系統(tǒng)會(huì)對(duì)生成的靜態(tài)庫的所有目標(biāo)文件執(zhí)行預(yù)鏈接操作蚯舱,預(yù)鏈接操作會(huì)將所有的目標(biāo)文件組合成為一個(gè)單獨(dú)的大的目標(biāo)文件改化。這樣根據(jù)以文件為單位的鏈接規(guī)則就會(huì)將靜態(tài)庫中的所有代碼全部都鏈接進(jìn)可執(zhí)行程序中去,但是這樣帶來的問題就是最后在dead code stripping時(shí)刪除不掉已經(jīng)鏈接進(jìn)來的那些沒有被任何地方使用過的OC類了枉昏。
  2. 對(duì)于引入的一些第三方靜態(tài)庫或者第三方的開源庫來說因?yàn)槲覀儫o法去改變其實(shí)現(xiàn)邏輯陈肛。如果這個(gè)靜態(tài)庫中沒有任何分類代碼的定義則正常引用即可,如果靜態(tài)庫中有分類方法的定義則單獨(dú)對(duì)這個(gè)靜態(tài)庫采用-force_load選項(xiàng)兄裂。

總之一句話:為了讓你的程序瘦身燥爷,盡量將代碼放到靜態(tài)庫中,不要使用-Objc和-all_load選項(xiàng)

為了驗(yàn)證上述方法的有效性懦窘,筆者對(duì)項(xiàng)目中的應(yīng)用做了一個(gè)測(cè)試:分別是有帶-ObjC選項(xiàng)和沒有帶-ObjC選項(xiàng)的情況下的應(yīng)用程序包中可執(zhí)行程序的大小從115M減少到95M前翎,減少了20M的尺寸。


歡迎大家訪問歐陽大哥2013的github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末畅涂,一起剝皮案震驚了整個(gè)濱河市港华,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌午衰,老刑警劉巖立宜,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異臊岸,居然都是意外死亡橙数,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門帅戒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灯帮,“玉大人,你說我怎么就攤上這事逻住≈痈纾” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵瞎访,是天一觀的道長腻贰。 經(jīng)常有香客問我,道長扒秸,這世上最難降的妖魔是什么播演? 我笑而不...
    開封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任冀瓦,我火速辦了婚禮,結(jié)果婚禮上写烤,老公的妹妹穿的比我還像新娘咕幻。我一直安慰自己,他們只是感情好顶霞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開白布肄程。 她就那樣靜靜地躺著,像睡著了一般选浑。 火紅的嫁衣襯著肌膚如雪蓝厌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天古徒,我揣著相機(jī)與錄音拓提,去河邊找鬼。 笑死隧膘,一個(gè)胖子當(dāng)著我的面吹牛代态,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疹吃,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蹦疑,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了萨驶?” 一聲冷哼從身側(cè)響起歉摧,我...
    開封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎腔呜,沒想到半個(gè)月后叁温,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡核畴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年膝但,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谤草。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡跟束,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出咖刃,到底是詐尸還是另有隱情泳炉,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布嚎杨,位于F島的核電站,受9級(jí)特大地震影響氧腰,放射性物質(zhì)發(fā)生泄漏枫浙。R本人自食惡果不足惜刨肃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望箩帚。 院中可真熱鬧真友,春花似錦、人聲如沸紧帕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽是嗜。三九已至愈案,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鹅搪,已是汗流浹背站绪。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丽柿,地道東北人恢准。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像甫题,于是被迫代替她去往敵國和親馁筐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

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