一、LinkMap文件分析
說明:LinkMap數據是根據文章《LinkMap文件分析》中方法實驗實測數據。
如何獲得LinkMap文件
1.在XCode中開啟編譯選項Write Link Map File \n
XCode -> Project -> Build Settings -> 把Write Link Map File選項設為yes,並指定好linkMap的存儲位置
2.工程編譯完成后,在編譯目錄里找到Link Map文件(txt類型)
在Build Settings里Path to Link Map File對應Link Map的路徑,以下是默認路徑:
/Users/xxx/Library/Developer/Xcode/DerivedData/YourApp/Build/Intermediates.noindex/YourApp.build/Debug-iphoneos/YourApp.build/YourApp-LinkMap-normal-arm64.txt
LinkMap里有了每個目標文件每個方法每個數據的占用大小數據,所以只要寫個腳本,就可以統計出每個.o最后的大小,屬於一個.a靜態鏈接庫的.o加起來,就是這個庫在APP里占用的空間大小。
LinkMap包含以下部分:
# Path: .../*.app文件
# Arch: arm64
# Object files: .../*.o文件
# Sections: Text和Data分類大小
# Symbols: 與Object files對應起來,是Object files的詳細Symbal信息,包括地址和大小
# Dead Stripped Symbols:
// linkmap.js var readline = require('readline'), fs = require('fs'); var LinkMap = function(filePath) { this.files = [] this.filePath = filePath } LinkMap.prototype = { start: function(cb) { var self = this var rl = readline.createInterface({ input: fs.createReadStream(self.filePath), output: process.stdout, terminal: false }); var currParser = ""; rl.on('line', function(line) { if (line[0] == '#') { if (line.indexOf('Object files') > -1) { currParser = "_parseFiles"; } else if (line.indexOf('Sections') > -1) { currParser = "_parseSection"; } else if (line.indexOf('Symbols') > -1) { currParser = "_parseSymbols"; } return; } if (self[currParser]) { self[currParser](line) } }); rl.on('close', function(line) { cb(self) }); }, _parseFiles: function(line) { var arr =line.split(']') if (arr.length > 1) { var idx = Number(arr[0].replace('[','')); var file = arr[1].split('/').pop().trim() this.files[idx] = { name: file, size: 0 } } }, _parseSection: function(line) { }, _parseSymbols: function(line) { var arr = line.split('\t') if (arr.length > 2) { var size = parseInt(arr[1], 16) var idx = Number(arr[2].split(']')[0].replace('[', '')) if (idx && this.files[idx]) { this.files[idx].size += size; } } }, _formatSize: function(size) { if (size > 1024 * 1024) return (size/(1024*1024)).toFixed(2) + "MB" if (size > 1024) return (size/1024).toFixed(2) + "KB" return size + "B" }, statLibs: function(h) { var libs = {} var files = this.files; var self = this; for (var i in files) { var file = files[i] var libName if (file.name.indexOf('.o)') > -1) { libName = file.name.split('(')[0] } else { libName = file.name } if (!libs[libName]) { libs[libName] = 0 } libs[libName] += file.size } var i = 0, sortLibs = [] for (var name in libs) { sortLibs[i++] = { name: name, size: libs[name] } } sortLibs.sort(function(a,b) { return a.size > b.size ? -1: 1 }) if (h) { sortLibs.map(function(o) { o.size = self._formatSize(o.size) }) } return sortLibs }, statFiles: function(h) { var self = this self.files.sort(function(a,b) { return a.size > b.size ? -1: 1 }) if (h) { self.files.map(function(o) { o.size = self._formatSize(o.size) }) } return this.files } } if (!process.argv[2]) { console.log('usage: node linkmap.js filepath -hl') console.log('-h: format size') console.log('-l: stat libs') return } var isStatLib, isFomatSize var opts = process.argv[3]; if (opts && opts[0] == '-') { if (opts.indexOf('h') > -1) isFomatSize = true if (opts.indexOf('l') > -1) isStatLib = true } var linkmap = new LinkMap(process.argv[2]) linkmap.start(function(){ var ret = isStatLib ? linkmap.statLibs(isFomatSize) : linkmap.statFiles(isFomatSize) for (var i in ret) { console.log(ret[i].name + '\t' + ret[i].size) } })
執行:
node linkmap.js LinkMap-normal-arm64.txt -hl
得到:
YourSDK(YourSDK-arm64-master.o) 122.76KB
二、xcodebuild總包大小分析
分析:Release 包(arm64)為什么比debug包(x86_64)大那么多?
分析步驟
1. 抽出YourSDK的arm64看包大小(1.6M)
lipo -thin arm64 -output YourSDK_arm64.a YourSDK.framework/YourSDK ➜ ls -al YourSDK_arm64.a -rw-r--r-- 1 yushu.lxy staff 1606208 11 18 15:58 YourSDK_arm64.a
2. 整體
size YourSDK_armv64.a __TEXT __DATA __OBJC others dec hex 86591 39492 0 1190617 1316700 14175c YourSDK_arm64.a(YourSDK-arm64-master.o) 30 240 0 4566 4836 12e4 YourSDK_arm64.a(libPods-YourSDK.a-arm64-master.o)
查看.a文件下.o文件大小
ar -t -v YourSDK_arm64.a rw-r--r-- 501/20 5776 Nov 18 15:58 2019 __.SYMDEF SORTED rw-r--r-- 0/0 1593168 Jan 1 08:00 1970 YourSDK-arm64-master.o rw-r--r-- 0/0 6976 Jan 1 08:00 1970 libPods-YourSDK.a-arm64-master.o
3. 詳細
size -m YourSDK_arm64.a YourSDK_arm64.a(YourSDK-arm64-master.o): Segment : 1316713 Section (__TEXT, __text): 68676(比debug的包小) Section (__TEXT, __objc_methname): 8393 Section (__TEXT, __cstring): 5091 Section (__TEXT, __objc_classname): 1636 Section (__TEXT, __objc_methtype): 1937 Section (__TEXT, __literal16): 128(只有debug包有) Section (__TEXT, __literal4): 8(只有debug包有) Section (__TEXT, __literal8): 144(比debug的包小) Section (__TEXT, __eh_frame): 16480 Section (__TEXT, __gcc_except_tab): 608 Section (__TEXT, __ustring): 42 Section (__TEXT, __const): 64 Section (__DATA, __const): 1560 Section (__DATA, __cfstring): 4736 Section (__DATA, __objc_classlist): 400 Section (__DATA, __objc_catlist): 8 Section (__DATA, __objc_protolist): 56 Section (__DATA, __objc_imageinfo): 8 Section (__DATA, __objc_const): 24224 Section (__DATA, __objc_selrefs): 2752 Section (__DATA, __objc_protorefs): 8 Section (__DATA, __objc_classrefs): 592 Section (__DATA, __objc_superrefs): 192 Section (__DATA, __objc_ivar): 252(比debug的包小) Section (__DATA, __objc_data): 4000 Section (__DATA, __data): 672 Section (__DATA, __bss): 32 Section (__LD, __compact_unwind): 11424 Section (__LLVM, __bundle): 1179193(+) total 1316700 total 1316713 YourSDK_arm64.a(libPods-YourSDK.a-arm64-master.o): Segment : 4838 Section (__TEXT, __text): 0 Section (__TEXT, __objc_classname): 30 Section (__DATA, __objc_classlist): 8 Section (__DATA, __objc_imageinfo): 8 Section (__DATA, __objc_const): 144 Section (__DATA, __objc_data): 80 Section (__LLVM, __bitcode): 4480 (+) Section (__LLVM, __cmdline): 86 (+) total 4836 total 4838
YourSDK SDK實際大小應該是:1316713-1179193 = 137KB(與LinkMap分析接近)
分析:
上面段數據LLVM bundle部分是release包比debug包大的原因,是因為release包包含了bitCode數據。
(1)bitcode是源碼和機器碼之間的中間形式。
(2)為什么蘋果要強推bitcode?開發者把bitcode提交到App Store Connect之后,如果蘋果發布了使用新芯片的iPhone,支持更高效的指令,開發者不需要做任何操作,App Store Connect自己就可以編譯出針對新產品優化過的app並通過App Store分發給用戶,不需要開發者自己重新打包上架。
(3)蘋果可以將bitcode代碼進行一些邏輯等價的轉換,使得代碼的執行效率更高,體積更小。
實驗:APP的project開啟bitcode和關閉bitcode,查看手機中app的實際大小,開啟bitcode的包更小。
size -m YourSDK_arm64.a YourSDK_arm64.a(YourSDK-arm64-master.o): Segment : 943490 Section (__TEXT, __text): 53184 Section (__TEXT, __objc_classname): 633 Section (__TEXT, __objc_methname): 7919 Section (__TEXT, __objc_methtype): 850 Section (__TEXT, __cstring): 7352 Section (__TEXT, __literal8): 64 Section (__TEXT, __gcc_except_tab): 1724 Section (__TEXT, __const): 112 Section (__DATA, __const): 1864 Section (__DATA, __cfstring): 4000 Section (__DATA, __objc_classlist): 200 Section (__DATA, __objc_protolist): 8 Section (__DATA, __objc_imageinfo): 8 Section (__DATA, __objc_const): 21760 Section (__DATA, __objc_selrefs): 1776 Section (__DATA, __objc_classrefs): 352 Section (__DATA, __objc_superrefs): 24 Section (__DATA, __objc_ivar): 620 Section (__DATA, __objc_data): 2000 Section (__DATA, __data): 96 Section (__DATA, __bss): 40 Section (__DATA, __common): 8 Section (__LD, __compact_unwind): 15968 Section (__LLVM, __bundle): 822914 total 943476 total 943490
分析結論
(1) LinkMap分析的大小不包含打入framework中的bundle信息,建議用LinkMap分析的包大小至少要加上bundle的大小。
(2)經多次安裝實驗,真實安裝包后, APP大小增長是比LinkMap給出的包大小大的。
(3)給外部的包大小信息,暫時跟大家保持一致“使用LinkMap給出的數據”。
