iOS可執行檔案壓縮瘦身法的實現解析

cocoachina發表於2015-02-07

縮減iOS安裝包大小是很多中大型APP都要做的事,一般首先會對資原始檔下手,壓縮圖片/音訊,去除不必要的資源。這些資源優化做完後,我們還可以嘗試對可執行檔案進行瘦身,專案越大,可執行檔案佔用的體積越大,又因為AppStore會對可執行檔案加密,導致可執行檔案的壓縮率低,壓縮後可執行檔案佔整個APP安裝包的體積比例大約有80%~90%,還是挺值得優化的。下面介紹一下在研究可執行檔案過程中發現的可以優化的點。研究的過程使用了linkmap,linkmap的介紹跟生成可以參考另一篇文章—iOS可執行檔案的組成。

編譯選項

1.編譯器優化級別

Build Settings->Optimization Level有幾個編譯優化選項,release版應該選擇Fastest, Smalllest,這個選項會開啟那些不增加程式碼大小的全部優化,並讓可執行檔案儘可能小。

2.去除符號資訊

Strip Debug Symbols During Copy 和 Symbols Hidden by Default 在release版本應該設為yes,可以去除不必要的除錯符號。Symbols Hidden by Default會把所有符號都定義成”private extern”,具體意思和作用我還不清楚,有待研究,但設了後會減小體積。這些選專案前都是XCode預設選項,但舊版XCode生成的專案可能不是,可以檢查一下。

其他優化還可以參考蘋果的官方文件—CodeFootprint.pdf
第三方庫統計

專案裡會引入很多第三方靜態庫,如果能知道這些第三方庫在可執行檔案裡佔用的大小,就可以評估是否值得去找替代方案去掉這個第三方庫。我們可以從linkmap中統計出這個資訊,我寫了個node.js指令碼,可以通過linkmap統計每個.o目標檔案佔用的體積和每個.a靜態庫佔用的體積,並進行排序。詳見這裡(需翻牆)。
ARC->MRC

有人提出用ARC寫的程式碼編譯出來的可執行檔案是會比用MRC大的,原因大致是ARC程式碼會在某些情況多出一些retain和release的指令,例如呼叫一個方法,它返回的物件會被retain,退出作用域後會被release,MRC就不需要,彙編指令變多,機器碼變多,可執行檔案就變大了。還有其他細節實現的區別,先不糾結了。

那用ARC究竟會增大多少體積?我覺得從彙編指令的增多減少去算是很難算準確的,這東西涉及細節太多,還是得從統計的角度計算。做了幾個對比試驗,統計了幾個同時支援ARC/MRC的開源專案在開啟/關閉ARC的情況下__TEXT程式碼段的大小對比。只對比__TEXT程式碼段是因為:

ARC對可執行檔案大小的影響幾乎都是在程式碼段

可執行檔案會進行某種對齊,例如有些段在不足32K的時候填充0直到對齊32K,若用可執行檔案大小對比結果可能是對齊後的,不準確。

實驗資料:

結果是ARC大概會使程式碼段增加10%的size,考慮程式碼段佔可執行檔案大約有80%,估計對整個可執行檔案的影響會是8%。

可以評估一下8%的體積下降是不是值得把專案裡某些模組改成MRC,這樣程式的維護成本上升了,一般不到特殊情況不建議這麼做。
無用程式碼

在專案裡新建一個類,給它新增幾個方法,但不要在任何地方import它,build完專案後觀察linkmap,你會發現這個類還是被編譯進可執行檔案了。

按C++的經驗,沒有被使用到的類和方法編譯器都會優化掉,不會編進最終的可執行檔案,但object-c不一樣,因為object-c的動態特性,它可以通過類和方法名反射獲得這個類和方法進行呼叫,所以就算在程式碼裡某個類沒被使用到,編譯器也沒法保證這個類不會在執行時通過反射去呼叫,所以只要是在專案裡的檔案,無論是否又被使用到都會被編譯進可執行檔案。

對此我們可以通過指令碼,遍歷整個專案的檔案,找出所有沒有被引用的類檔案和沒有被呼叫的方法,在保證沒有其他地方動態呼叫的情況下把它們去掉。如果整個專案歷時很長,歷時程式碼遺留較多,這個清理對可執行檔案省出的空間還是挺可觀的。
類/方法名長度

觀察linkmap可以發現每個類和方法名都在__cstring段裡都存了相應的字串值,所以類和方法名的長短也是對可執行檔案大小是有影響的,原因還是object-c的動態特性,因為需要通過類/方法名反射找到這個類/方法進行呼叫,object-c物件模型會把類名,方法名列表都儲存下來。

可以考慮在編譯前把所有類和方法名進行混淆,把長名字替換成短名字,這樣做的好處除了縮小體積外,還對安全性有很大提升,別人拿到可執行檔案對它class-dump出來的結果都是混淆後的類和方法名,就無法從類和方法名中猜出某個方法是做什麼的,就難以掛鉤子進行hack。不過這樣有個缺點就是crash堆疊反解出來的堆疊方法名會是混淆後的,需要再加一層混淆->原名的轉換,實現和使用成本有點高。

實際上這部分佔用的長度比較小,中型專案也就幾百K,對安全性要求高的情況可以試試。
冗餘字串

程式碼上定義的所有靜態字串都會記錄在在可執行檔案的__cstring段,如果專案裡Log非常多,這個空間佔用也是可觀的,也有幾百K的大小,可以考慮清理所有冗餘的字串。另外如果有特別長的字串,建議抽離儲存成靜態檔案,因為AppStore對可執行檔案加密導致壓縮率低,特別長的字串抽離成靜態資原始檔後壓縮率會比在可執行檔案裡高很多。
CheckList

最後簡單把縮減iOS安裝包大小的各種方法列出來作為CheckList:

相關文章