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/YourSDKls -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数据

参考《关于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给出的数据”。

posted @ 2020-12-18 19:44  Xylophone  Views(1692)  Comments(0Edit  收藏  举报