貨拉拉 iOS 包大小優化探索與實踐

語言: CN / TW / HK

hll.png

一. 背景介紹

過去幾年,貨拉拉業務高速發展,作為核心業務入口的使用者端App,在業務與技術上也在與時俱進不斷升級迭代,以滿足使用者多樣化的需求,隨之而來也遇到了移動端開發同學都會遇到的問題:App需要“瘦身”了,站在使用者的角度,以下幾個指標是比較容易被感知到的:

  1. 安裝包大小
  2. 啟動速度
  3. 穩定性
  4. 耗損
  5. 流暢度

包大小意義非凡,它是App重要的基礎體驗指標之一,使用者要安裝App首先要面對的是App的包大小,它影響著使用者的安裝率,安裝成功率,解除安裝率,在2019谷歌開發者大會上,谷歌給出了一個很詳細的資料,包體大小每上升6MB,應用下載轉化率就會下降1%,不同地區轉化率略有差異,每減少10MB ,全球平均下載轉化率會提升1.75%。

本文將為大家介紹貨拉拉使用者端在優化安裝包大小方向做的一些探索與嘗試,希望能給大家在自家App包大小優化方面提供一些借鑑和思路。

  1. 包大小概念

App包大小可以理解成安裝包(即.app檔案)大小或者下載包(即.ipa檔案)大小,兩者的關係是,下載包(即:ipa檔案)是使用者從App Store商店下載時的壓縮包大小,要檢視該資料需要在App Connect後臺進行檢視,當壓縮包下載完成後會自動解壓,安裝到使用者的手機上,安裝完畢後就變成安裝包(即.app檔案),佔用使用者手機的磁碟空間;

一般而言,安裝包(即.app檔案)會比下載包(即.ipa檔案)大很多,安裝包大小優化後,下載包自然會相對減小,這裡我們將安裝包作為本次討論的技術指標。

  1. 重視包大小

一款App在迭代的過程中,隨著新需求的不斷豐富,勢必會造成包體積逐漸增大,App早期由於功能比較簡單,而且優化空間有限,使用者感知並不明顯,包大小可能不受重視,但隨著App功能不斷豐富和多元化,包大小自然水漲船高,當大到一定值後,則會在一些方面產生消極影響:

  1. App Store OTA 下載大小限制:

蘋果公司為了避免使用者的運營商流量超限,限制了使用者直接通過運營商流量從 AppStore 下載 App 的最大大小,這將一定程度上影響新使用者轉化以及老使用者的使用者體驗和升級率,該限制大小歷年都會做出一定的調整 :

由2017年9月的 100M 到2019年5月的 150M,iOS13系統釋出之後,iOS13 及以上使用者可以使用流量下載超出 200MB 的 App, 但需要使用者「設定」選擇策略「超過 200MB 請求許可」,但iOS13 系統以下使用者仍然無法流量下載。

  1. App __TEXT 段大小限制:

除了以上限制,蘋果公司對App的可執行檔案(即Mach-O檔案)的__TEXT段大小也做了嚴格限制要求,超出這個限制將無法通過App Store的稽核:

  1. iOS 7 之前,二進位制檔案中所有的 __TEXT 段總和不得超過 80 MB;
  2. iOS 7.x 至 iOS 8.x,二進位制檔案中每個特定CPU架構的 __TEXT 段不得超過 60 MB;
  3. iOS 9.0 之後,二進位制檔案中所有的 __TEXT 段總和不得超過 500 MB。
  1. App效能問題

App包大小的劣性增長,主要原因有較多冗餘的程式碼檔案,資原始檔(如圖片,字型檔案,db檔案等)以及低效的程式碼邏輯,這些因素不僅影響包大小,還波及App的其他效能指標,如啟動時長,流暢度(FPS),Crash率,甚至業務的深層次迭代效率;

二. 蘋果為我們做了哪些

  1. App Thinning

App Thinning 蘋果公司推出的一項可以改善 App 下載程序的新技術,主要是為了解決使用者下載 App 耗費過高流量的問題 ,它可以讓 App Store 和作業系統在安裝、更新及執行 iOS 或者 watchOS 的 App 等場景時,通過一系列的優化,儘可能減少安裝包的大小,僅下載所需的資源,減少 App 的佔用空間,從而節省裝置的儲存空間。

App Thinning 有三種方式,包括:App SlicingBitcodeOn-Demand Resources

  1. App Slicing,很多應用需要在不同尺寸的裝置上執行,針對這些不同的裝置,它們內含不同的獨立資源,而大部分是你的裝置不需要的,App store會針對不同的裝置建立不同的變體,適用到不同的裝置。

需要注意,圖片資源必須提供多解析度的圖片採用Asset Catalog管理,才能在Slicing中發揮作用。

  1. On-Demand Resources,按需載入資源,在下載App的時候,App中包含的不重要資源先不下載,等到需要時,再由系統向蘋果的伺服器Server傳送請求,下載指定的資源包,以減少初裝 App 的包大小,主要用在遊戲多關卡場景,玩家解鎖遊戲的特定關卡後,可以下載新關卡及這個關卡相關的特定資源。此外,玩家已經通過的關卡可以被移除以便節約裝置上的儲存空間。
  2. Bitcode,一個編譯好的程式的中間表示形式,App開啟Bitcode,傳到 iTunes Connect後會在 App Store中進行連結和編譯,生成一箇中間表現形式(Bitcode),再將這個Bitcode編譯為可執行的64位或32位程式,若後續蘋果新增一種CPU架構,那麼蘋果可重新優化我們程式的二進位制檔案以進行適配,而不需我們重新提交新版本到App Store上,此優化不明顯(需要注意的是,使用Xcode14構建時包時,若開啟BitCode,蘋果將限制提交到App Store)。

三. 我們在包大小上的探索

1. 分析安裝包

安裝包構成由以下三個部分:

  1. Mach-O可執行檔案:主要包含專案的核心業務程式碼及靜態庫
  2. 資原始檔:

圖片資源:包括Assets.car,Bundle檔案,GIF,JPG,PNG,JSON等檔案

靜態WEB資源:JS,CSS,HTML

檢視資源:XIB,Storyboard

音影片資源:mp4,mp3,caf等

其他資源:簽名檔案,字型檔案,PLIST檔案,db檔案,國際化資原始檔等等

  1. Framework,應用程式依賴的動態庫

我們從以上三個部分進行一定程度的優化,下圖是我們收集的優化策略,有些需依賴其他部門的支援,如資源動態化,需要後端或者運維的支援,在實施中存在可行性及收益比兩方面考慮,具體能否落地需看公司實際情況。

2. 編譯器優化

  1. 去掉無用架構

可通過Xcode的Excluded Architectures項新增需排除的CPU架構,常見的架構及機型搭配:

  • i386 架構,支援32位模擬器,已不再使用;
  • x86_64 架構,支援64位模擬器,現在都是64位的模擬器;
  • armv7, armv7s 架構,支援32位真機,基本上淘汰了;
  • arm64 架構,支援64位真機,在iPhone5S開始支援,蘋果使用者基本都是這個架構

可以在Xcode的Excluded Architectures配置項增加armv7,armv7s,讓我們構建的包只支援arm64架構,雖然蘋果有App Slicing機制也會做指定機型的切割,但此操作優化了我們平時的編譯時長和構建產物大小;

  1. Optimization Level

  1. 選項None[-O0]:編譯器不會優化程式碼,意味著更快的編譯速度和更多的除錯資訊,預設在 Debug 模式下開啟;
  2. Fast[-O, O1]: 編譯器會優化程式碼效能並且最小限度影響編譯時間,此選項在編譯時會佔用更多的記憶體;
  3. Faster[-O2]:編譯器會開啟不依賴空間/時間折衷所有優化選項。在此,編譯器不會展開迴圈或者函式內聯。此選項會增加編譯時間並且提高程式碼執行效率;
  4. Fastest[-O3]:編譯器會開啟所有的優化選項來提升程式碼執行效率。此模式編譯器會執行函式內聯使生成的可執行檔案變得更大。一般不推薦使用此模式;
  5. Fastest Smallest[-Os]:編譯器會開啟除了會明顯增加包大小以外的所有優化選項。預設在 Release 模式下開啟;
  6. Fastest, Aggressive Optimization[-Ofast]:啟動 -O3中的所有優化,可能會開啟一些違反語言標準的一些優化選項。一般不推薦使用此模式。
  1. Strip Link Product

Strip Link Product會受到Deployment Postprocessing設定的影響,只有當Deployment Postprocessing設定為YES時,該選項才會生效,當Strip Link Product為YES可以幫我們優化掉Strip Style中設定的不需要的符號資訊,但除錯App時斷點不會中斷,App Crash後控制檯也無法看到具體的類名和方法名,一般Debug模式設定為NO,Release模式設定YES。

Strip Style是我們需要去除的符號的型別的選項,包括:

1)All Symbols: 去除所有符號,一般是在主工程中開啟;

2)Non-Global Symbols: 去除一些非全域性的 Symbol(保留全域性符號,Debug Symbols

同樣會被去除),連結時會被重定向的那些符號不會被去除,此選項是靜態庫 / 動態庫的建議選項;

3)Debug Symbols: 去除除錯符號,去除之後將無法斷點除錯;

  1. Make Strings Read-Only

複用字串字面量,減少不必要的生成,也是一種優化形式。

  1. Dead Code Stripping

消除無效程式碼,C/C++/Swift 等靜態語言編譯器會在 link 的時候移除未使用的程式碼,對於OC等動態語言是無效的。

  1. Link-Time Optimization

此設定有三個選項值:

1)預設No 不開啟連結期優化

2)Monolithic 生成單個 LTO 檔案,每次連結重新生成,無快取高記憶體消耗,引數LLVM_LTO=YES

3)Incremental 生成多個 LTO 檔案,增量生成,低記憶體消耗,引數 LLVM_LTO=YES_THIN(推薦)

LTO帶來的優化:

1)將一些函式內聯化:不用進行呼叫函式前的壓棧、呼叫函式後的出棧操作,提高執行效率與棧空間利用率;

2)去除了一些無用程式碼:如果一段程式碼分佈在多個檔案中,但是從來沒有被使用,普通的 -O3 優化方法不能發現跨中間程式碼檔案的多餘程式碼,因此是一個區域性優化。但是 Link-Time Optimization可以在連結時發現跨中間程式碼檔案的多餘程式碼;

3)對程式有全域性的優化作用:這是一個相對廣泛的概念。舉個例子來說,如果一個 if 方法的某個分支永不可能執行,那麼在最後生成的二進位制檔案中就不應該有這個分支的程式碼。

  1. Asset Catalog Compiler

此配置的Optimization含三個選項,空、time和space,選擇space可以優化包大小。

3. 資源優化

  1. 刪除無用資源

App包中的資源有:未使用圖片(GIF,PNG,JPG,webp等),音影片,Plist檔案等等,可以使用業內工具進行掃描,比如:

1)LSUnusedResources,一個視覺化客戶端工具,使用簡單,不做贅述;

2)fdupes命令查詢專案中的重複檔案,原理是對比不同檔案的簽名,簽名相同的檔案就會判定為重複資源,此方式可能存在誤判,建議人工複核後進行刪除;

這裡分享一下我們使用Shell指令碼掃描未使用資源的邏輯,由於我們的專案採用了元件化,每個元件的資源都是獨立隔開的,所以掃描時需要每個元件單獨掃碼,逐一清理,每個元件掃描後可得到:

1)確定未使用的資源路徑(這裡包括GIF,PNG,JPG,Webp等型別檔案),此類檔案可直接刪除;

2)可能未使用的資源路徑,一般因檔名帶有數字(可能字串拼接的檔名),此類檔案需再次確認

``` 值得一提的是,採用指令碼形式會比視覺化工具更靈活:

1)可根據需要對指令碼匹配的正則進行修改來支援掃描識別出更多型別檔案;

2)掃描的檔案可輸出檔案路徑,用於再次確認;

3)如專案採用的外部指令碼的自動化構建打包,我們的掃描指令碼可移植到打包流程(一般用於優化外部三方庫)中的資原始檔;

4)可繼續對指令碼進行拓展,將掃描的指定圖片進行壓縮,這一點也可以嵌入到CI流程中 ```

每次資源清理後,迭代幾個版本就會發現工程中又會產生很多無用資源,其中以圖片資源居多,所以可以將資源優化這個能力作為優化常規項,定期清理,這一點我們在後面防劣方案中會提到,我們經過定期掃描清理,截止目前我們在清理無用資源這一塊的收益是6.73M,後續我們會繼續執行,保證專案專案的資源利用率。

我們的指令碼流程:

通過find命令遞迴查詢元件工程各目錄,grep進行檔名匹配判斷檔案是否被使用,需要注意,由於我們工程主要採用了OC,定義控制代碼時,資源引入方式只考慮@""和xib形式引入的資原始檔(如圖片),可以根據本身專案的圖片引入規則對匹配控制代碼進行調整:

`` // 以下為程式碼片段 # 如果圖片是在.imageset目錄下,則不能用圖片名判斷,而是要用.imageset名,因為圖片名可能與.imageset名不同,而程式碼中使用的卻是.imageset名 if [[ $png =~ ".imageset" ]];then v=echo $png | grep -Eo "/Users(.*).imageset"match_name=basename $v | awk -F '.imageset' '{print $1}'` fi

pic_size=`wc -c $png | awk '{print $1}'`

referenced=false
# 判斷圖片名稱是否引用到
if grep -q "$match_name" "$maybeImageSentencePath"; then
    referenced=true
fi

# 如果圖片是帶有數字的,就判斷為可能是拼接的圖片
contaT=$(echo $match_name | grep "[0-9]")
if [[ "$contaT" != "" ]];  then
    MaybeUnusedCount=`expr $MaybeUnusedCount + 1`
    echo "$png"  >> $maybeUnusedImageFilePath
    echo "${png}圖片可能未用到"
    # 將使用到的圖片,超過閥值寫入檔案
    if [ $pic_size -gt $k_base_size ];then
        echo "大於${k_base_size},寫入檔案"
        img_kb_size=`awk 'BEGIN{printf "%.2f\n",'$pic_size'/'$k'}'`
        echo "$png ${img_kb_size}KB" >> $mayUnusedExceedPicPath
    fi 
    continue
fix

# 圖片不在賦值文字中
if ! $referenced ; then
    UnusedCount=`expr $UnusedCount + 1`
    echo "$png"  >> $unusedImageFilePath
    echo "${png}圖片未使用"
    TotalSize=`expr $TotalSize + $pic_size`
    # 將使用到的圖片,超過閥值寫入檔案
    if [ $pic_size -gt $k_base_size ];then
        echo "大於${k_base_size},寫入檔案"
        img_kb_size=`awk 'BEGIN{printf "%.2f\n",'$pic_size'/'$k'}'`
        echo "$png ${img_kb_size}KB" >> $unusedExceedPicPath
    fi  
    continue
fi



# 圖片用到了,在imageset目錄下,有三張png圖片,則需要去掉1x圖片
if [[ $png =~ ".imageset" ]];then
    path=`echo $png | grep -Eo "/Users(.*).imageset"`
    files=$(ls $path)
    count_flag=0
    for filename in $files
    do
        if [[ $filename =~ ".png" ]]; then 
            count_flag=`expr $count_flag + 1`
        fi 
        if [[ "$count_flag" == "3" ]];then 
            echo $png >> $usedContain3Pic
        fi 
    done 
fi

done ```

這裡我們還添加了通過掃描出超過某個閾值的圖片,主要用於檢視專案中的大圖,大圖的優化空間會更大;

``` # 將使用到的圖片,超過閥值寫入檔案

if [ $pic_size -gt $k_base_size ];then

    echo "大於${k_base_size},寫入檔案"

    img_kb_size=`awk 'BEGIN{printf "%.2f\n",'$pic_size'/'$k'}'`

    echo "$png ${img_kb_size}KB" >> $exceedPicPath

fi

```

另外,第一次實施時會發現存在很多檔案未使用,這裡可以分享一個簡單的指令碼,批量進行刪除:

``` delete_path="專案路徑"

rm -fr $delete_path;mkdir $delete_path

filePath="這裡填前面掃描後得到的資源路徑集合的文字路徑"

cat $delete_path | while read line

do
if [[ "$line" =~ ".imageset" ]];then img_dir=echo "$line" | grep -Eo "/Users(.*).imageset" rm -fr $img_dir # else # rm "$line" fi # cp $line $delete_path

done ```

  1. 圖片壓縮

圖片資源的最好用 Asset Catalog來進行管理,Xcode 構建過程中,在執行到compile asset catalog時,會利用構建Asset Catalog的actool外掛會對Asset Catalog中的png圖片進行解碼,得到 Bitmap 資料,然後再運用actool的編碼壓縮演算法進行編碼壓縮處理,如果放入到Assets中的是JPG圖片,最終也會轉成png圖片存放到Assets.car中。

外掛actool的壓縮演算法有 lzfse,palette_img,deepmap2,deepmap_lzfse,zip。

需要注意的是,無失真壓縮是變換圖片的編碼壓縮演算法來減少大小,並未改變畫素資料,所以無失真壓縮的圖片不能優化Assets.car的最終大小,但有失真壓縮是可以,這裡說明下我們在圖片壓縮方面做的幾點探索。

常見的圖片壓縮方式有:

  1. TinyPng,它是一個網頁工具,有失真壓縮,需要聯網,不支援批量處理;
  2. TingPNG4Mac,屬於Mac的客戶端工具,基於TinyPng實現的;
  3. ImageOptim,客戶端工具,支援無失真壓縮和有失真壓縮兩種形式,可自定義壓縮方式;
  4. pngquant,命令列工具,有失真壓縮,編寫指令碼批量壓縮,支援自定義壓縮質量引數;
  5. Guetzli,針對數碼影象和網頁影象的JPEG編碼器,支援JPG圖片的有失真壓縮;

由於專案在迭代過程中使用到的圖片在切圖,選圖上的疏忽可能會將較大圖片引入進來,所以我們會定期掃描,檢查專案中的大圖是否合格,保證需要的圖片是最優的,經此階段,我們在專案中圖片資源壓縮的收益如下:

| 元件 | 收益大小 | | --------- | ----- | | 主工程 | 1.8M | | User子元件 | 4.72M | | Common子元件 | 9.17M | | 搬家子元件 | 3M | | 地圖元件 | 2.66M | | IM元件 | 0.2M | | 其他三方庫 | 0.3M |

通過第一步的無用資源刪除後,專案中存在的圖片資源基本上是我們必需的了,此時我們依然採用Shell指令碼對各元件的圖片進行壓縮,通過調研我們發現,pngquant工具是我們需要的,我們採用了pngquant命令來壓縮專案中的圖片。

pngquant壓縮原理:將24位或32位的RGBA的PNG圖轉換成8位的PNG圖,保留全部的alpha通道,生成的圖片相容所有現代web瀏覽器;

官方說明:

壓縮命令:

$ pngquant --quality=20-30 檔名

當quality設定在20-30時,第一次壓縮後,在不影響圖片質量的情況下,大小一般可優化在60%左右,對於本身很小的圖片也可以進行壓縮(不過建議將壓縮前後的對比圖讓UI設計師把關)

這個是我們批量壓縮User元件圖片前後對比:

對於單張圖片,視覺上確實看不出壓縮的痕跡,參考下圖:

壓縮前:72K

壓縮後:16K

我們指令碼流程:

指令碼程式碼:

注意:有些圖片本身很小,壓縮後反而會增大,所以需要判斷一下是否需要壓縮,指令碼壓縮邏輯為:

`` check_files=find $input_dir -name '*.png'` for line in $check_files

do

pngquant --quality=20-30 $line
pre_name=`echo $line | awk -F '.png' '{print $1}'`
pre_name="${pre_name}-fs8.png"
pic_size1=`wc -c $line | awk '{print $1}'`
pic_size2=`wc -c $pre_name | awk '{print $1}'`
if [[ $pic_size2 -lt $pic_size1 ]];then 
    mv $pre_name $line
else 
    echo "原圖${line},壓縮圖$pre_name"
    echo $pic_size1
    echo $pic_size2
    echo "壓縮後圖片大小反而變大,放棄此次壓縮"
    cp -fr $line $cannot_zip_dir
    rm $pre_name
fi

done

echo "將壓縮後的圖片拷貝出來..." check_files=find $input_dir -name '*.png' for line in $check_files do
cp $line "${zip_bundle}/" done

rm -fr $origin_dir echo "執行完畢!!!" exit 1 ```

  1. 靜態資源站點化

也可以將一些本地資源託管到自己的伺服器或者CDN上,這樣也可以優化安裝包大小,如H5資源,字型檔案,較大的圖片,音影片,App主題資源,需要注意的是,最好在不影響App啟動效率的基礎上評估實施;

  1. 採用webp格式圖片

採用webp的理由是WebP 壓縮率較高,肉眼看不出差異,支援有損和無損兩種壓縮模式,根據 Google 的測試,無失真壓縮後的 WebP 比 PNG 檔案少了 26%的體積,有失真壓縮後的 WebP 圖片相比於等效質量指標的 JPEG 圖片減少了 25%~34% 的體積,WebP 支援 Alpha 透明和 24-bit 顏色數,不會像 PNG8 那樣因為色彩不夠而出現毛邊。

WebP 與JPG,PNG 相對比,編解碼在CPU的消耗上以及效率上會差一些,如果是伺服器圖片,編碼過程是在伺服器後臺進行,那麼對影響較大的是解碼過程,圖片載入速度慢一些,這需要根據專案的實際情況在效能和體積上進行評估取捨。

注意:iOS本身暫不支援WebP格式載入,可引入SDWebImage/WebP

轉Webp工具:

  1. iSpart騰訊出品,GUI工具;
  2. webp命令列工具: 使用Homebrew進行安裝:

$brew install webp

  1. 圖示的優化

這種優化方式屬於編碼過程中需要注意的點,單個的收益不會太大,實踐起來會比較繁瑣,可根據專案要求進行這類精細化處理。

一般方式有:

  1. 使用 tint color 精簡單色圖示

單色圖示處理(將單色圖示渲染模式為UIImageRenderingModeAlwaysTemplate,如返回按鈕,tabbar icon圖示,會忽略自身顏色資訊採用父控制元件tintColor顏色渲染)

  1. 圖示字型(IconFont)替換單色圖示
  2. 相似圖示整合

4. Mach-O優化

安裝包中的Mach-O可執行檔案包含我們的核心業務程式碼,它的大小是由程式碼量來決定的,所以一般情況下,是需要找出專案中無用方法和無用類進行刪除,當然,業界也有對從其他角度對Mach-O的大小進行優化的。

  1. 靜態篩查方案

設定Xcode的Build SettingWrite Link Map File為Yes,再指定Path to Link Map File的指向路徑可得到編譯後的LinkMap檔案,這個檔案包括Object File,Section,Symbols,包含了所有的類和方法,然後通過otool命令匯出專案Mach-O中的__objc_selrefs、 __objc_class_list & __objc_class_refs 做差集找到未使用的Objc類及方法。

Mach-O中片段:

linkMap檔案片段:

如果程式碼採用 C 、C++ 等靜態語言編寫程式碼時,編譯期已經確定了基本的程式碼邏輯,所以編譯器會幫助我們將沒有使用到的程式碼標記為 Dead code 最終不會打包到安裝包中。但Objc 是典型的動態語言,很多邏輯都是在執行時決議的,通過靜態掃描的方式會存在較大誤差,還需要二次確認,抖音對於這靜態結果初篩的得到未使用類的準確性只有 24% (總樣本 264 個,命中 64 個)。

2.動態篩查方案

  1. 基於插樁的行級別程式碼覆蓋率:

基於 GCOV 或者 LLVM Profile 二進位制的插樁方案可以實現在執行時收集插樁資料來指導無用程式碼的刪除。但插樁方案侷限性也顯而易見,插樁會劣化二進位制本身的大小和效能,同時原生的插樁方案是無法過審上線。資料收集只能侷限於線下。

  1. 基於 Runtime 的輕量級執行時「類覆蓋率」方案:

Objc 的類首次呼叫類初始化時,+initialize 被執行,系統會自動標記已被呼叫,在 metaClass 中 data 的 flags 欄位第 29 位就存著這個這個狀態。可以使用 flags & RW_INITIALIZED 獲取。iOS14 之後這個值的獲取方式有變化

```

define RW_INITIALIZED (1<<29)

bool isInitialized() {

return getMeta()->data()->flags & RW_INITIALIZED;

} ```

上報的資料可以讓我們瞭解我們線上真實的 Class 使用情況,對得到的資料不僅可以用來刪減未使用的程式碼。還可以分辨使用率低的場景,如果是低頻且必須的場景可以考慮使用跨端技術這種對原生包大小影響比較小的方案實現。而如果這些場景是某個滲透率很低的需求可以考慮直接下線為其他需求做置換。

5. Framework優化

安裝包中Framework目錄存放著我們專案依賴的動態庫,一般情況下蘋果給我們的建議是不超過六個,由於我們的專案是元件化的,有些三方庫,二方庫通過Cocoapods引入時,是以動態庫的形式引入的,動態庫雖然本身產物比靜態庫小,但最終我們的安裝包中,動態庫以幾近拷貝的形式進入到我們的包中,而靜態庫會經過靜態連結進入到我們的可執行檔案Mach-O中。

App依賴Dyld構建包流程:

1.探索靜態庫/動態庫對包的影響

條件:同等情況下,使用:AFNetworking,SDWebImage,SensorsAnalyticsSDK分別作為靜態庫和動態庫方式引入工程:

| | | IPA包大小 | 備註 | | --------------------------------------- | ------- | ------ | -- | | AFNetworkingWebImageSensorsAnalyticsSDK | 作為動態庫引入 | 3.6M | | | 作為靜態庫引入 | 1M | | |

通過實驗,我們發現動態庫方式引入後對安裝包整體的影響還是比較大的,如果專案中存在較多的動態庫,可以考慮轉靜態庫進行優化。

6. 一些編碼習慣的建議

1.封裝&重構

整理專案中的重複方法和邏輯,下沉到能夠通過的模組中,還可以考慮對專案中的老舊耦合高的模組進行重構,一定程度上能減少程式碼量。

2.字串常量管理,命名管理

專案中的區域性字串常量轉成全域性常量字串,統一管理,字串的命名設定一定規範,最好不要太長,文字比較長的字串可以轉成文字進行讀取或者伺服器變數下發。

3.Class Method vs C 函式

通常我們對於一些基礎和通用的函式會採用工具類的方式對外暴露。使用類方法完成功能。但當我們採用 Class Method, 這種方式在編譯的時候需要生成 Class 的類結構。呼叫的方法會通過 objc_methodSend。如果採用 C 函式的方式可以減小這部分的開銷。如果只是自己元件內部使用的私有的功能性函式還是建議使用 C 函式的方式實現。

4.Property vs IVAR

Objc 對於 Class 的 property,會自動的生成 set、get 方法,比如這個 property 是 Class 的私有屬性的時候,我們可以直接使用 ivar 來代替 property。減小這部分的包大小開銷。這裡需要注意,當我們使用 property 的 getter 實現 LazyLoad 或者 setter 存在一些其他副作用的時候還是需要保留 property 的。

5.儘量避免程式碼片段複製貼上

專案中可能有些方法或者函式可以被複用,但往往由於一些原因,如方法不能直接訪問或引數不一致等原因最終被拷貝出去另起新方法,特別是元件化專案,元件之間的通訊是有一定限制的,容易產生這類問題,建議將這些方法儘可能抽象出來以支援多種情況,並抽離或下沉到公共模組中。

四. 防劣化方案設計

  1. 圖片大小把關

    1. @1x圖不用整合,只需要2x,3x;
    2. 定義圖片名規範,以英文命名,格式為: 圖片分類_模組名_功能名,如:ic_orderdetail_scan
    3. 中小型圖片建議用png, 可指令碼先行壓縮或重新切圖,再引入到專案中,完成開發後,如有做壓縮先讓美工進行視覺驗收;
    4. 所有圖片儘量放xcassets統一管理;
  2. 資源閾值預警

  1. 在構建釋出包前,掃描增量資原始檔,超過設定的閾值,啟動報警機制,評估該資源引入的必要性或引入方式的合理性。
  2. 將掃描各元件未使用的資源的指令碼植入打包流水線,打包過程中會執行掃描未使用資源,一旦掃描到就將資源清單傳送給模組負責人。
  1. 三方庫管理

針對指定的三方SDK圖片資源,通過自動化構建釋出包時,考慮在打包的打包流水線嵌入指令碼進行清理,壓縮,以防外部資源的劣化。

  1. 模組預警機制

模組大小增量超過設定的閾值時,啟動報警機制,對該模組增加的大小進行評估合理性,是否有優化空間,啟發大家反思。

  1. 建立良好的編碼規範

制定良好的編碼規範,在團隊中推廣,實行開發人員的結對Review程式碼(按照制定好的編碼規範),每個需求合併到Release釋出分支前,先Review稽核通過後再進行合併。

五. 結論

本次主要是從編譯器優化,資原始檔清理,圖片壓縮,類/方法掃描,Framework治理、編碼習慣建議,防劣化設計幾個方面介紹了貨拉拉iOS使用者端在App包大小方面優化的實踐,有些思路是大多數App瘦身優化通用的,App包優化是一個循序漸進的過程,建議先易後難,比如先從優化資原始檔方面入手,而且大多數情況下,資原始檔優化的收益是最大的,最好每次優化後進行迴歸測試保證線上穩定,最後希望本文能給大家在自家App包大小優化時提供一些借鑑和思路。

參考:

Alibaba.com包大小優化
探索 iOS 編碼對包大小的影響
抖音品質建設 - iOS 安裝包大小優化實踐篇