iOS包大小计算
一、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给出的数据”。