iOS應用程序瘦身的靜態庫解決方案

為什么要給程序瘦身?

隨著應用程序的功能越來越多,實現越來越復雜,第三方庫的引入,UI體驗的優化等眾多因素程序中的代碼量成倍的增長,從而導致應用程序包的體積越來越大。當程序體積變大后不僅會出現編譯流程變慢,而且還會出現運行性能問題,會增加應用下載時長和消耗用戶的移動網絡流量等等。因此在這些眾多的問題下需要對應用進行瘦身處理。

一個應用程序由眾多資源文件和可執行程序文件組成,資源文件的優化不在本文探討范圍。本文主要討論對可執行程序代碼瘦身的方法。

對可執行程序代碼瘦身主要就是想辦法讓程序中不會被調用的源代碼不參與編譯或鏈接。我們可以通過一些源代碼分析工具來查找哪些函數或者類方法沒有被調用并從代碼中刪除掉來解決編譯鏈接前的瘦身問題。這些分析工具也不在本文的討論范圍內。應用程序在編譯時會對工程中的所有代碼都執行編譯處理并生成目標文件。而在鏈接階段則會根據程序代碼中對符號的引用關系來將所有相關的目標文件鏈接為一個大的可執行程序文件,并且在鏈接階段鏈接器會優化掉所有沒被調用的C/C++函數代碼,但是對于OC類中的沒有調用的方法則不會被優化掉。所以為了對可執行程序在編譯鏈接階段進行瘦身處理就需要了解源代碼的編譯鏈接規則。這也是本文所要介紹的針對工程通過靜態庫的形式進行編譯和鏈接的方式來減少可執行程序代碼的尺寸。您可以從文章:《深入iOS系統底層之靜態庫介紹》中詳細的了解到靜態庫的編譯鏈接過程,以及相關的技術細節。

一個瘦身的例子!

為了驗證和具體的實踐,我在github上建立了一個項目:YSAppSizeTest。您可以從這個項目中看到如何對工程進行構建以實現程序的瘦身處理。

在示例項目中同一個Workspace中分別建立ThinApp和FatApp兩個工程,這兩個工程實現的功能是一樣。在整個應用程序中分別定義了CA、CB、CC、CD、CE一共5個OC類,定義了一個UIView(Test)分類,還有定義了兩個C函數:libFoo1和libFoo1。

整個應用程序中只使用了CA和CC兩個OC類,以及調用了UIView(Test)分類方法,以及調用了libFoo1函數,并且同時都采用導入靜態庫的形式。因為這兩個工程對文件的定義和分布策略不同使得兩個應用程序的最終可執行代碼的尺寸是不相同的。

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

  1. FatApp工程依賴并導入了FatAppLib靜態庫工程。
  2. CA,CB兩個類都定義在主程序工程中。
  3. CC,CD,CE三個類,以及UIView(Test)分類,還有libFoo1,libFoo2兩個函數都定義在FatAppLib靜態庫工程中。
  4. CC,CD兩個類定義在同一個文件中,CE類則定義在單獨的文件中。
  5. FatApp工程的Other Linker Flags中設置了 -ObjC選項。

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

  1. ThinApp工程依賴并導入了ThinAppLib靜態庫工程。
  2. 主程序工程就是一個殼工程。
  3. CA,CB,CC,CD,CE5個類,以及UIView(Test)分類,還有libFoo1,libFoo2兩個函數都定義在ThinAppLib靜態庫工程中。
  4. 上述的5個類都分別定義在不同的文件中。
  5. ThinApp工程的Other Linker Flags中沒有設置-ObjC選項。

上述兩個工程的程序被Archive出來后,FatApp可執行程序的尺寸是367KB,而ThinApp可執行程序的尺寸是334KB。通過一些工具比如Mach-O View或者 IDA可以看出:FatApp中5個OC類的代碼以及libFoo1函數還有UIView(Test)分類的代碼都被鏈接進可執行程序中;而ThinApp中則只有CA,CC兩個類以及libFoo1函數還有UIView(Test)分類的代碼被鏈接進可執行程序中。在ThinApp中雖然沒有使用-Objc鏈接選項,但是靜態庫中的分類也被鏈接進可執行程序中。

應用程序工程構建規則

根據對項目中的文件定義和引用策略以及相關的理論基礎我們可以按照如下的規則來構建您的應用程序:

  1. 盡量將所有代碼都移植到靜態庫中,而主程序則保留為一個殼程序。具體操作方法是建立一個Workspace,然后主程序工程就只有默認創建工程時的代碼,所有新加入的代碼都建立并存放到靜態庫工程中去,然后通過工程依賴來引入這些靜態庫工程,或者借助一些工程化工具比如Cocoapods來實現這種拆分和引用處理。主程序工程中只保留AppDelegate的代碼,其他代碼都一致到靜態庫中。然后在AppDelegate中的相關代碼處調用靜態庫中定義的業務代碼。

  2. 按業務組件對工程進行解耦每個組件是一個靜態庫工程。靜態庫中的每一個文件中最好只有一個類的實現,并且類的分類實現最好和類實現編寫在同一個文件中,相同功能的代碼以及可能都會被調用的代碼盡量存放在一個文件中。

  3. 不要在主程序工程中使用-ObjC和-all_load兩個選項而改為用-force_load 來單獨指定要執行加載的靜態庫。-ObjC和-all_load選項會把主程序工程以及所依賴的所有靜態庫中的工程中的全部代碼都鏈接到可執行程序中而不管代碼是否有被調用過或者使用過。而force_load則只會將指定的靜態庫中的所有代碼鏈接到可執行程序中,當然force_load如果沒有必要也盡量不要使用。

  4. 盡量減少在靜態庫中定義OC類的分類方法,如果一定要定義分類方法則可以將分類方法定義在和類定義相同的文件中,或者將分類方法定義在一個一定會被調用和引用的實現文件中。因為根據鏈接規則靜態庫中的分類是不會被鏈接進可執行程序中的,除非使用了上述的三個鏈接選項。如果將分類代碼單獨的定義在一個文件中的話則可以通過在分類的頭文件中定義一個內聯函數,內聯函數調用分類實現文件中的一個dumy函數,這樣只要這個分類的頭文件被include或者import就會把整個分類的實現鏈接到可執行程序中去。一般情況下我們在靜態庫中建立分類那就表明一定會被某個文件引用這個分類,從而實現整個文件的鏈接處理。在分類中定義的這兩個函數則因為沒有被任何地方調用,因此會在鏈接優化中將這兩個函數給優化掉。這樣就使得即使我們不用-ObjC選項也能將靜態庫中的分類鏈接到可執行程序中去。最后需要注意的是在每個分類中定義的這兩個函數名最好能夠唯一這樣就不會出現符號重名沖突的問題了。

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

//分類中定義的方法

@end

/*
  通過在分類的頭文件中定義一個內聯函數,內聯函數調用分類實現文件中的一個dumy函數,這樣只要這個分類的頭文件被include或者import就會把
  整個分類的實現鏈接到可執行程序中去。一般情況下我們在靜態庫中建立分類那就表明一定會被某個文件引用這個分類,從而實現整個文件的鏈接處理。
  而在分類中定義的這兩個函數則因為沒有被任何地方調用,因此會在鏈接優化中將這兩個函數給優化掉。這樣就使得即使我們不用-ObjC選項也能
  將靜態庫中的分類鏈接到可執行程序中去。最后需要注意的是在每個分類中定義的這兩個函數名最好能夠唯一這樣就不會出現符號重名沖突的問題了。
 */
extern void _cat_UIView_XXX_Impl(void);
inline void _cat_UIView_XXX_Decl(void){_cat_UIView_XXX_Impl();}


------------------------------------------------------------
//分類文件的實現文件UIView+XXX.m
#import "UIView+XXX.h"

@implementation UIView (XXX)

//分類的實現代碼

@end

void _cat_UIView_XXX_Impl(void){}


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

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

  1. 除了可以通過-force_load來加載指定靜態庫中的所有代碼外。我們還可以在構建靜態庫時,在靜態庫的工程的Build Settings中將Perform Single-Object Prelink 中的開關選項打開。當這個開關打開時,系統會對生成的靜態庫的所有目標文件執行預鏈接操作,預鏈接操作會將所有的目標文件組合成為一個單獨的大的目標文件。這樣根據以文件為單位的鏈接規則就會將靜態庫中的所有代碼全部都鏈接進可執行程序中去,但是這樣帶來的問題就是最后在dead code stripping時刪除不掉已經鏈接進來的那些沒有被任何地方使用過的OC類了。
  2. 對于引入的一些第三方靜態庫或者第三方的開源庫來說因為我們無法去改變其實現邏輯。如果這個靜態庫中沒有任何分類代碼的定義則正常引用即可,如果靜態庫中有分類方法的定義則單獨對這個靜態庫采用-force_load選項。

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

為了驗證上述方法的有效性,筆者對項目中的應用做了一個測試:分別是有帶-ObjC選項和沒有帶-ObjC選項的情況下的應用程序包中可執行程序的大小從115M減少到95M,減少了20M的尺寸。


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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容