扣丁書屋

Alibaba.com瘦包40MB——業界最全的iOS包大小技術總結

前言

包大小是衡量APP性能的一項重要指標,它直接影響用戶的下載點擊率(包太大不想下)、下載安裝成功率(下載慢不用了)、APP卸載率(太占空間先刪掉)。包大小的計算邏輯很簡單,它是各種類型的文件占用磁盤大小相加。APP瘦身的技術卻很復雜,代碼文件的復雜度和編譯器策略決定了可執行文件的大小,業務功能和工程架構決定了代碼文件的復雜度。iOS APP瘦身,需要掌握的技能有XCode構建技術、LLVM編譯器技術、CocoaPods構建技術、圖片壓縮技術、持續集成技術。本文總結提煉了Alibaba.com App的瘦身的技術和策略,系統化地介紹APP瘦身的業務價值、分析技術、瘦身技術、防劣化機制,讓讀者可以系統化地了解APP瘦身的技術體系。本文還基于實踐經驗,介紹各種瘦身技術的ROI,讓讀者可以避免踩雷,將資源浪費在效果不佳的技術上。希望對你有所幫助。??

業務價值

包體大小每上升6MB,應用下載轉化率就會下降1%

在2019谷歌開發者大會上,谷歌給出了一個很詳細的數據,包體大小每上升6MB,應用下載轉化率就會下降1%。不同地區轉化率略有差異,APK包體大小每減少10MB ,全球平均下載轉化率會提升1.75%,新興國家代表印度和巴西下載轉化率提升2.0%以上,高端市場代表美國和德國下載轉化率提升1.5%。

?上圖標題:APK減少10MB,在不同國家轉化率增長

數據來源:google play 內部數據

詳細材料:?白鯨出海:2019谷歌開發者大會首日看點

上述數據調研分析報告是2019年以前,已經有所滯后,僅供參考

包大小影響下載轉化率可能有3個原因:

1.蜂窩網絡環境下,用戶不愿意支付流量費用。包大小超過200MB時,App Store會彈框提醒用戶下載可能會產生流量費用。

2.下載時間太長,用戶不愿意等就取消了

3.下載過程中出現網絡連接問題?雖然Google Play沒有給出不同APP類目的數據,但是從以上三個原因推斷,不同類目包大小對下載轉化率的影響估計差不多。App Store的用戶人群比較高端,可以參考美國和德國的數據。?

20%的人因為存儲空間有限而卸載應用程序

clevertap在2021年做了一項調查,他們調查了2000多個移動應用程序用戶,詢問了他們卸載移動應用程序的主要原因,其中有20%的人因為存儲空間有限而卸載應用程序。

最主要的3個原因是:

1.他們不再使用該應用程序

2.有限的存儲空間

3.太多的廣告。

?詳細材料:clevertap:Why Users Uninstall Apps?

?App Store 發布和下載限制

兼容iOS8的App,主二進制文件的Text段不能超過60MB, 否則將無法提交App Store。App Store下載包超過200MB,無法使用蜂窩流量下載和更新。

分析技術

APP瘦身最終目標是減少App Store的安裝包大小和下載包大小,但研發階段對比XCode構建包大小會更方便,需要理清楚他們之間的口徑差異。

結果指標:App Store安裝包大小和下載包大小

查看路徑是App Store Connect->TestFlight->交付版本->構建版本元數據->App Store文件大小

過程指標:XCode構建包大小

XCode構建產物ipa包的大小和App Store的安裝大小口徑差異很大,通過模擬Apple處理的流程可以得到它們的關系。開發者上傳的ipa包里有多個架構的產物,多套尺寸的圖片資源,蘋果會進行裁剪和二次分發,轉化為App Store下載的ipa包。

構建產物ipad包大?。红o態庫二進制文件(arm64、armv7)、動態庫(arm64、armv7)、asset.car(@2x、@3x)、其他資源文件App Store的安裝大?。红o態庫二進制文件(單架構)、動態庫(單架構)、asset.car(單尺寸)、其他資源文件

使用lipo工具拆分單架構

lipo "originalExecutable" -thin arm64 -output "arm64Executable"

使用assetutil工具拆分asset

xcrun --sdk iphoneos assetutil --scale 3 --output "$targetFolder/Assets3.car" "$sourceFolder/Assets.car"

構建包組成

學習如何治病前,得先了解人體的構成。同樣的道理,我們首現要了解iOS工程結構和IPA包結構。

iOS工程結構:iOS工程由殼工程和Pod模塊組成,模塊有靜態庫和動態庫兩種類型。殼工程的構成有主Target、Apple插件Target。模塊內部的構成有源代碼文件(OC、C、C++的.h和.m)、nib、bunlde、xcassets、多語言文件、各種配置文件(plist、json)。IPA包結構:iOS上傳到App Store是IPA包,IPA包解壓后是一個文件夾,內部由各種類型的文件構成,主要包括MachO可執行文件、.framework(動態庫)、Assets.car、.appex(Apple插件)、.strings(多語言)、.bundle、nib、json、png...。從iOS工程到IPA包:iOS工程構建為IPA包,核心的變化是編譯和文件拷貝,靜態庫里的源代碼會被編譯為MachO可執行文件,xcassets文件夾會被轉化為Assets.car,其他都可以簡單理解為文件拷貝。

通過分析構建包的組成,可以判斷有哪些優化空間。如果動態庫占比太高,就可能有大量可裁剪代碼。建議根據資源大小敏感程度劃分,比如:MachO executeable(包含所有靜態庫)、動態庫、Apple Extension、assets.car、bundle、nib、音頻、視頻。

Pod模塊大小

APP瘦身會涉及大量業務模塊,離不開業務團隊的參與。因此,我們需要分析出每個Pod模塊的大小,從而可以橫向比較各個業務團隊的包大小占比。靜態庫和動態庫計算的原理不同。對于靜態庫,先解析linkmap數據,計算出Pod模塊代碼大小,在解析Pods-targetName-resource.sh的資源拷貝代碼,計算出拷貝到Pod模塊的資源大小。對于動態庫,先使用lipo拆分動態庫的二進制文件,計算出單架構的代碼大小,然后再計算動態庫framework內的資源文件,得到動態庫的資源文件大小。

運行時Objc類覆蓋率

如果能知道App運行時有哪些類被使用過,就可以下線掉無用的模塊或代碼文件,Objc類覆蓋率指標可以幫到我們。APP運行時,某1個Pod模塊被加載的類數量除以所有類數量,可以稱為這個模塊的Objc類覆蓋率。核心技術是判斷一個類是否被加載過,下面介紹一個經過線上驗證的輕量級方案。ObjC的類第一次被使用時會調用+initialize方法,類被加載過后cls->isInitialized會返回True。isInitialized方法讀取了metaClass的data變量里的flags,如果flags里的第29位為1,則返回True。

// objc-class.mm
Class class_initialize(Class cls, id inst) {
    if (!cls->isInitialized()) {
        initializeNonMetaClass (_class_getNonMetaClass(cls, inst));
    }
    return cls;
}


// objc-runtime.h
#define RW_INITIALIZED        (1<<29)
bool isInitialized() {
    return getMeta()->data()->flags & RW_INITIALIZED;
}

未被使用的圖片資源

技術原理是先解析出所有拷貝到構建產物的資源文件,再解析出代碼中實際引用到的資源文件,兩者的差集就是無用資源。

第一步獲取全量資源文件。在Cocoapos工程中,“Pods-targetName-resource.sh”腳本負責拷貝Pod里的文件資源到構建產物,包括所有文件類型bundle、xcassets、json、png。解析該腳本可以得到每個Pod模塊都拷貝了哪些圖片資源。

// Pods-targetName-resource.sh
install_resource "${PODS_ROOT}/APodName/APodName.framework/APodName.bundle"
install_resource "${PODS_ROOT}/BPodName/BPodName.framework/BPodName.xcassets"
install_resource "${PODS_ROOT}/BPodName/BPodName.framework/xxx.png"

第二步,獲取代碼中實際引用到的資源文件。OC代碼中引用資源文件都是以字符串字面量的形式聲明,構建后存放在Mach-O文件"__cstring" section。利用strings解析framework的二進制文件就可以得到代碼中所有的字符串聲明,然后基于代碼的各種引用方式匹配“xxx”,"xxx@2x.png","xxx.png","xxx.bundle/xxx.png"-

strings executable | grep 'xxx' > cstrings.txt

編譯時未被引用的類

iOS編譯的產物是Mach-o格式的,文件里 __DATA __objc_classrefs 段記錄了所有引用過的類的地址,__DATA __objc_classlist段記錄了所有類的地址,兩者Differ可以得到未被引用類的地址。然后將地址符號化,就可以得到未被引用類信息。因為Objc是動態語言,如果使用runtime動態調用某個class,這種情況掃描不出來。(比如Target Action和 JS Core)

otool -v -s __DATA __objc_classrefs xxxMainClient  #讀取__DATA Segment中section為__objc_classrefs的符號
otool -v -s __DATA __objc_classlist xxxMainClient #讀取__DATA Segment中section為__objc_classlist的符號
nm -nm xxxMainClient

?掃描未使用類的開源工具?

瘦身技術

包大小瘦身可以從純技術視角瘦身,也可以從邏輯視角瘦身。

從純技術視角有兩種思路,第一種思路是優化編譯邏輯, 第二種思路是刪減各種其他類型(非編譯產物)的文件。編譯優化對業務邏輯無侵入式,風險和成本比較低,但收益通常也不高。刪減文件則比較復雜,刪減資源文件收益高但成本不小,逐個刪減源碼文件風險高且收益小。

相比而言,從邏輯的視角瘦身效果會更明顯。站在邏輯的視角,工程是由許多功能模塊組成的,主要可以分為業務功能模塊(首頁、搜索、收銀臺、訂單)、基礎功能模塊(網絡庫、圖片庫、中間件、各種三方庫...)。大型工程通常幾百甚至上千個模塊組成,小模塊的有幾十KB,大的模塊有1MB~10+MB。功能模塊內聚性強,當某個功能模塊可以廢棄或被替代時,整體下線的風險和成本比較低,ROI很高。

瘦身技術大圖

根據項目經驗,我梳理出各種優化方法的ROI,下面依次介紹。

??組件瘦身

組件瘦身的ROI:類加載率0%的組件 > 無用功能組件 > 重復功能組件“類加載率0%的組件”是一個完整的Pod模塊,模塊的代碼和資源可以被整體刪除,ROI最高?!盁o用功能組件”通常是模塊內某個文件目錄,需要處理一些耦合,梳理資源的歸屬,ROI其次?!爸貜凸δ芙M件”需要適當重構,遷移成本和風險最高,ROI相對較低。

類加載率0%的組件

“類加載率0%的組件”是指運行時沒有任何一個類被加載過的組件,它在運行過程中完全沒有用到,可以整體下線。統計類加載率時一般會進行采樣,有些低頻業務組件可能會誤報,下線前需要和業務Owner二次確認。?

??

無用功能組件

“無用功能組件”是邏輯上已經沒用的組件,它可能代碼還有耦合,但邏輯已經沒用,通過重構就可以下線。比如AB實驗沒效果的業務代碼、往年大促的業務代碼、業務改版后遺留的老業務、已經過時的三方庫。

重復功能組件

對于大規模團隊,不同業務線可能會引入“重復功能組件”,比如圖片選擇器、緩存庫、UI組件。重復的功能組件會帶來不必要的包大小壓力,同時也會帶來維護成本。

資源瘦身

資源瘦身ROI:大資源>有損壓縮>重復資源>iconfont>多語言文案瘦身>無用資源

大資源

對大資源進行單點優化收益很大,優先分析100KB以上的資源。比如音頻文件,我們工程中音頻鈴聲900KB,優化后去掉了700KB。

有損壓縮

XCode構建時會做“compile asset catalog”,會重新對圖片進行無損壓縮。因此使用imageoptim等工具進行無損壓縮效果不明顯,其中壓縮png圖片沒有效果,壓縮jpg圖片有一定效果。根據實踐經驗,icon做有損壓縮并不影響視覺體驗,壓縮率可以達到70%~80%。比如使用tinypng算法壓縮一張425.9 KB的png圖片,壓縮后79.8 KB,壓縮率達到81%。業界有不少png壓縮工具,我們使用到的有tinypng、pngquant、pngcrush、optipng(無損)、advpng。實測發現不同的圖片,壓縮效果最好的工具是不一樣,所以壓縮圖片時可以用每個工具嘗試,然后取效果最好的。需要注意的是,壓縮APNG圖片可能會變成非動圖,盡量避免對APNG圖片進行壓縮,壓縮前先識別是否APNG圖片。

# 判斷是否APNG格式,APNG格式不壓縮
function isApng() {
    ret=`grep "acTL" "$1"`
    lastWord="${ret##* }"
    if [[ $lastWord == "matches" ]]; then
        echo "YES"
    else
        echo "NO"
    fi
}
# 使用各種工具壓縮png圖片
# pngquant
pngquant  256 -s5 --quality 70 --output "${tmpFileName}" -- "${tmpOrigName}" 
# pngcrush
pngcrush -brute -rem alla -nofilecheck -bail -blacken -reduce -cc -- "$tmpOrigName" "$tmpFileName"
# optipng
optipng -o6 -i0 -out "$tmpFileName" -- "$tmpOrigName"
# advpng
cp "$tmpOrigName" "$tmpFileName"
advpng -4 -z -- "$tmpFileName"

# 逐個比較壓縮效果,保留效果最好的壓縮圖片
sizeBefore=`stat -f%z "${tmpOrigName}"`
sizeBefore=`expr $sizeBefore`
sizeNow=`stat -f%z "${tmpFileName}"`
sizeNow=`expr $sizeNow`

if [ "$sizeNow" -lt "$sizeBefore" ]; then
  # 大小變小了
  echo "[advpng] $tmpOrigName"
  retImg="$tmpFileName"
fi
# 記錄hash值
img_sum=`/usr/bin/shasum -a 256 "$retImg" | cut -d " " -f1`
cache_file="$cacheFolder/$img_sum.png"

無用資源

業務長期迭代會積累許多無用的資源。通過代碼靜態掃描,可以分析出沒有被引用的圖片資源。我們有工程無用資源有12MB,其中有20個模塊無用資源超過100KB。?

重復圖標

不同業務需求會引入重復資源,長期積累也會造成很大浪費。解決方案是在構建時計算資源的哈希值,去重相同哈希資源,并保留源文件名和哈希值的映射表。運行時Hook 資源加載的”imageNamed“方法,根據映射表替換資源名稱。

ODR

iOS不能像Android一樣,運行時更新Framework。iOS的ODR技術(On Demand Resource)提供了運行時動態下載的能力。如果啟動用不到的資源文件,可以通過ODR處理。

iconfont

iconfont支持縮放、修改顏色,它size小,適合用于箭頭、占位圖等圖標場景,使用iconfont可以減少包大小也能提高開發視覺體驗的統一性。首先,基于設計規范出一套完整的iconfont,封裝iconfont組件,提供易用的API。然后禁止新業務場景增加圖片資源,團隊形成開發習慣后再逐步替換存量的圖片。

多語言文案

對于國際化App,多語言文案也是大頭。我們App支持20個語種,每個語種4000多條文案,文案總大小6MB,瘦身后減少了2MB。

編譯優化

編譯優化ROI:Optimization Level > 動態庫復用主二進制靜態庫> 鏈接器產物壓縮

XCode編譯優化選項中,Optimization Level效果最明顯,建議所有模塊構建都開啟Oz選項。如果APP有動態庫,并且依賴了openssl等基礎靜態庫,建議和APP工程共享,并通過EXPORTED_SYMBOLS_FILE的選型,確保動態庫中需要用到的符號都在編譯過程保留。如果團隊技術儲備強,可以自研鏈接器產物壓縮技術,效果也很大。具體效果如下。

主工程 OC模塊 62880行代碼 C++模塊
LTO <10KB 開啟前 modulesize:2020KB, MainProjectSize:6463KB 開啟后 modulesize:1582KB MainProjectSize:6888KB 收益:13KB /
剝離符號表:Strip Linked Product <10KB / /
精簡編譯產物Oz:Optimization Level 100-200KB Os:2261KB Oz:2020KB 收益:241KB /
Symbols Hidden by Default <10KB 優化前:2020KB 優化后:2020KB 收益:0KB /
剔除未引用的C/C++/Swift代碼:Dead Code Strippin <10KB / /
Asset Catalog Compiler <10KB
動態庫共享APP基礎靜態庫 / / 動態庫A依賴了公共的靜態庫庫B,通過設置只導出必要符號 收益:1367KB
C++全局靜態數組改成動態分配內存 / 收益:200KB
C++去掉RTTI支持 / 收益:1MB

?精簡編譯產物Oz:Optimization Level

Optimization Level多個級別,-Oz比-O3的編譯產物體積小10%左右。設置-Oz以后,XCode會優化連續的匯編指令,從而減少二進制大小,但副作用是執行速度會變慢。C++工程建議都開啟。


主工程Release
Optimization Level :-Oz

Framework工程
Optimization Level :-Oz

LTO(OC/C++)

LTO 是在LLVM鏈接時,優化跨模塊調用代碼。根據大家分享的經驗,它不同APP包大小優化效果差異較大,需要具體測試。另外它也有一些副作用,建議只在Release包開啟。LTO介紹

優點:

●將一些函數內聯化

●去除了一些無用代碼

●對程序有全局的優化作用

缺點:

●降低編譯鏈接速度,只建議在打正式包時開啟

●降低 link map 可讀性(出現XX-lto.thin的類)

主工程設置


主工程Release
Link-Time Optimization 設置為Incremental

framework工程Release
Link-Time Optimization 設置為Incremental

動態庫復用主二進制靜態庫

C++動態庫經常用到一些基礎庫比如openssl、libyuv、libcurl,他們一般是靜態庫。如果動態庫引用了靜態庫,它編譯時默認會內嵌靜態庫的所有符號。雖然我們可以在動態庫中設置只導出需要用到的靜態庫符號,但是有可能多個動態庫都用到了同一個基礎庫,這樣還是會造成基礎庫的冗余。比如openssl大小1MB,如果A、B兩個動態庫依賴了openssl,APP也引用了openssl,最終ipa包實際有3個openssl,有2MB大小是冗余的。

這種場景下,最佳解決方案是共享符號表,讓動態庫可以調用主二進制的基礎庫符號,從而可以去掉內置的靜態庫。只要修改XCode的Link配置,無需額外的代碼開發。

動態庫工程:

1、設置當遇到未定義的函數時,動態查找APP主二進制符號表。

2、關閉bitcode


Other Linker Flags -> -undefined dynamic_lookup
Enable Bitcode -> No

3導出動態庫需要調用的外部符號,寫到一個文件exported_symbols內

nm -u  xxx.framework/xxx > exported_symbols.txt

APP工程:

1、配置需要導出exported_symbols文件內的所有符號,避免編譯時動態庫需要用到的符號被strip掉。

2、關閉bitcode。


// exported_symbols.txt是需要被導出的符號文件路徑
EXPORTED_SYMBOLS_FILE -> exported_symbols.txt
Enable Bitcode -> No

如果C++動態庫里依賴了外部靜態庫,該靜態庫的符號默認會被全部導出。實際上動態庫只用了其中的部分符號,可以設置導出符號的白名單,從而減少導出沒有用到的靜態庫符號。

注意事項:

1APP和framwork工程都要關閉bitcode,否則編譯不過。2如果多個動態庫都使用同一個基礎庫,導出的符號表需要取并集。3動態庫升級要及時更新符號表,基礎庫升級要測試兼容性。

鏈接器產物壓縮(黑科技)

iOS工程構建產物是MachO文件,MachO文件中的TEXT段存放了各種只讀的數據段,__cstring段存放了普通的C String,__objc_methtype和__objc_methname存放了Objc的方法簽名和方法名。比入Objc代碼中聲明的@"Hello world",底層會產生一個CFString,構建后存放在__cstring中。這些數據很占空間,一般工程至少會有10MB以上,壓縮的收益很可觀。我們上線后,App Store安裝包大小從191MB優化到174MB,減少了16MB。

技術原理:

鏈接時將TEXT段數據移到__DATA段并壓縮,運行時先執行解壓代碼,解壓TEXT段數據存到自定義段中,將代碼中對字符串的引用的地址修正為解壓后的自定義段。

剝離符號表:Strip Linked Product

Strip Linked Product設置會剝離特定的符號,Debug環境不要設置YES,否則調試時看不到符號。

主工程Release
Deployment Postprocessing :YES
Strip Linked Product :YES
Strip Style :All Symbols(剝離所有符號表和重定向信息)

Framework工程
Deployment Postprocessing :YES
Strip Linked Product :YES
Strip Style :Non-Global Symbols(剝離包括調試信息等非全局的符號,保留外部符號)

說明:

1、靜態庫不能將Strip Style 設置為All Symbols,因為剝離了所有符號的靜態庫是無法被正常鏈接的

2、去除符號不影響 dSYM 文件中的符號信息,查看崩潰日志時,可以從 dSYM 文件中找對應符號

Symbols Hidden by Default

Symbols Hidden by Default用于設置符號默認可見性,如果設置為YES,XCode會把所有符號都定義為”private extern”,包大小會略有減少。動態庫設置為NO,否則會有鏈接錯誤。


主工程Release
Symbols Hidden by Default :Yes

Framework工程 靜態庫/動態庫
Symbols Hidden by Default :NO

剔除未引用的C/C++/Swift代碼:Dead Code Stripping

Dead Code Stripping開啟后會在鏈接時移除未使用的代碼,它對靜態語言C/C++/Swift有效,對動態語言OC無效。

主工程
Dead Code Stripping :Yes

Asset Catalog Compiler

Optimization有三個選項,空、time和Space,選擇Space可以優化包大小

主工程
Asset Catalog Compiler->Optimization設置為space

C++只導出必要符號:Symbol Visibility

C++的靜態庫和動態庫都只導出必須的符號,默認設置為隱藏所有符號,然后用Visibility Attributes單獨控制需要導出的符號

默認隱藏所有C++符號

Other C++ Flags->添加-fvisibility=hidden

設置需要導出的符號


__attribute__((visibility("default"))) void MyFunction1() {} 
__attribute__((visibility("default"))) void MyFunction2() {} 
....

代碼下線

根據Objc類覆蓋率統計的結果,可以逐步下線掉未被使用的類。代碼文件的量級比較小,下線代碼需要仔細確認,避免引起功能問題或crash,ROI比較低。

??Flutter專項

Flutter是單獨構建的,引入的內容包括Flutter引擎產物、Flutter業務代碼產物。APP如果引入Flutter,需要對Flutter進行專項瘦身。

精簡編譯產物Oz:Optimization Level

-Oz選項相比Os,收益預估11%,但首屏性能1%~9%的損耗

Dart符號剔除和混淆

根據官方文檔,可以通過--dwarf-stack-trace選項去除Dart標準的調試符號。通過--obfuscate 選項,可以將較長的符號替換為短符號,副作用是符號會被混淆。實踐效果:Release環境下,--dwarf-stack-trace和--obfuscate選項開啟后減少14%的大小

Flutter icon搖樹優化

--tree-shake-icons實踐效果:Alibaba.com App開啟后減少了300KB左右大小

去除NOTICES文件

實踐效果:壓縮前700KB,壓縮后80KB

防劣化機制

增量標準和集成卡口

?包大小瘦身的技術就像各種減肥運動,跑步、跳舞、燃脂瑜伽。但想達到減肥的目標,只靠運動是不夠的,還得控制卡路里的攝入,管住嘴的人最后才能減肥成功。因此,我們還需要”新增卡路里的規范”(包大小增量規范)和監工(集成卡口)。持續集成當中加入一個卡口插件,分析構建包的linkemap文件得到模塊的大小可,然后對比基線數據,如果違反了包大小增量標準,則禁止集成。

包大小增量規范(參考):

1.基線數據:基于特定版本,通過上文提到的”計算模塊大小“的方法,計算出每個模塊的基線數據

2.存量模塊:模塊增量超過基準數據100KB時禁止集成。設置補償機制,如果能對存量模塊瘦身,可以抵消新增的模塊大小。對于特殊情況可以走特殊審批,加入審批流程是啟發大家反思。增加那么多是否有價值?有沒有帶入不必要的資源?

3.新模塊:新業務功能需要走審批流程,評估新業務價值和包大小增量是否合理。

橫向對比業務健康度

當包大小從技術層面已經優化到極致時,想要進一步瘦身,只能從業務價值的角度去挖掘。如果一個模塊磁盤尺寸大,用戶使用量卻少,那可以認為它對業務的價值較小。因此我們可以定義一個技術指標來衡量模塊單位大小的業務貢獻度?;谌莘e率指標,我們就可以橫向對比各個業務,要求容積率低的業務做包大小瘦身,或下線不太重要的業務功能。

容積率 = Business PV / Business size

總結

總結一下包大瘦身的實施路徑。第一步,制定目標,跟蹤APP下載轉化率(App Store Connect Analytics)、APP安裝包大小、XCode構建包大小等結果指標。第二步,建設分析體系,包括Pod模塊大小分析、Objc類覆蓋率分析、無用圖片資源分析等。第三步,根據ROI使用各項瘦身技術,組件瘦身>資源瘦身>編譯優化>代碼下線.第四步,建設防劣化機制,包括增量標準、集成卡口能力、健康度分析。


https://mp.weixin.qq.com/s/I6DH5RvkMh_-bxGpkAKBPA

最多閱讀

iOS 性能檢測新方式?——AnimationHitches 1年以前  |  22531次閱讀
快速配置 Sign In with Apple 3年以前  |  6305次閱讀
APP適配iOS11 4年以前  |  5059次閱讀
App Store 審核指南[2017年最新版本] 4年以前  |  4899次閱讀
所有iPhone設備尺寸匯總 4年以前  |  4796次閱讀
使用 GPUImage 實現一個簡單相機 3年以前  |  4561次閱讀
開篇 關于iOS越獄開發 4年以前  |  4186次閱讀
在越獄的iPhone設置上使用lldb調試 4年以前  |  4093次閱讀
給數組NSMutableArray排序 4年以前  |  4009次閱讀
使用ssh訪問越獄iPhone的兩種方式 4年以前  |  3708次閱讀
UITableViewCell高亮效果實現 4年以前  |  3705次閱讀
關于Xcode不能打印崩潰日志 4年以前  |  3632次閱讀
iOS虛擬定位原理與預防 1年以前  |  3629次閱讀
使用ssh 訪問越獄iPhone的兩種方式 4年以前  |  3447次閱讀

手機掃碼閱讀
18禁止午夜福利体验区,人与动人物xxxx毛片人与狍,色男人窝网站聚色窝
<蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>