文章目录
为什么要分析 App 文件体积?
作为一个 iOS 开发者,通常会需要关心 App 体积的大小,这将会是影响用户在 App Store 看到 App 时,是否决定下载的因素之一,如果 App 的体积太大,可能会使得用户放弃下载 App,特别是在用户使用蜂窝网络的时候。
对于一个 iOS App 来说,它的体积通常由以下几个部分组成:
- 二进制可执行文件
- 资源文件
- 第三方 Framework
对于资源文件和第三方 Framework 来说,我们可以直接通过统计文件大小的方式来统计它们所占用的磁盘空间,但是对于二进制文件来说,我们该如何去统计它里面所包含的代码大小组成呢?
如何分析?
非常幸运的,Xcode 为我们提供了 Link Map 这样的一个统计文件来展示代码在经过编译之后,在可执行二进制文件中的实际占用体积大小,通过分析这个文件,我们就可以精确地知道,对应到每一个代码文件,它在最终生成的可执行二进制文件中所占的体积。
为了让 Xcode 能输出我们所需要的 Link Map 文件,需要在 Xcode 进入到工程的设置界面,选择需要输出 Link Map 的 Target,选择 Build Settings Tab,在搜索框中输入“link map”(注意 link 和 map 中需要包含一个空格),然后就可以看到以下设置:
将 Write Link Map File 修改为 Yes,并且将 Path to Link Map File 修改为一个更容易找到的路径,例如在这里我修改为我的下载目录中的 test-linkmap.txt。
一个典型的 Link Map 文件内容如下所示:
# Path: /Users/ohdarling/Library/Developer/Xcode/DerivedData/LinkMapAnalyzer-atehkijmusbhcoahevnvjewudpxp/Build/Products/Debug/FastLinkMapAnalyzer.app/Contents/MacOS/FastLinkMapAnalyzer
# Arch: x86_64
# Object files:
[ 0] linker synthesized
[ 1] /Users/ohdarling/Library/Developer/Xcode/DerivedData/LinkMapAnalyzer-atehkijmusbhcoahevnvjewudpxp/Build/Intermediates.noindex/LinkMapAnalyzer.build/Debug/FLMAApp.build/Objects-normal/x86_64/LMWindowController.o
[ 2] /Users/ohdarling/Library/Developer/Xcode/DerivedData/LinkMapAnalyzer-atehkijmusbhcoahevnvjewudpxp/Build/Intermediates.noindex/LinkMapAnalyzer.build/Debug/FLMAApp.build/Objects-normal/x86_64/ViewController.o
[ 3] /Users/ohdarling/Library/Developer/Xcode/DerivedData/LinkMapAnalyzer-atehkijmusbhcoahevnvjewudpxp/Build/Intermediates.noindex/LinkMapAnalyzer.build/Debug/FLMAApp.build/Objects-normal/x86_64/VerticallyCenteredTextFieldCell.o
......
# Sections:
# Address Size Segment Section
0x100002CC0 0x0000CAF2 __TEXT __text
0x10000F7B2 0x000000C0 __TEXT __stubs
0x10000F874 0x00000150 __TEXT __stub_helper
0x10000F9C4 0x00002BD0 __TEXT __objc_methname
......
# Symbols:
# Address Size File Name
0x100002CC0 0x00000080 [ 1] -[LMWindowController windowDidLoad]
0x100002D40 0x00000059 [ 1] -[LMWindowController windowShouldClose:]
0x100002DA0 0x00000170 [ 2] -[ViewController viewDidLoad]
0x100002F10 0x00000070 [ 2] -[ViewController setRepresentedObject:]
0x100002F80 0x00000140 [ 2] -[ViewController saveURL:forKey:]
0x1000030C0 0x00000220 [ 2] -[ViewController retriveURLForKey:]
0x1000032E0 0x00000120 [ 2] -[ViewController chooseLinkMap:]
......
其中 # 符合标识各个区块的内容,Link Map 中的包含的区块内容有:
- Path: Link Map 文件对应的可执行二进制文件
- Arch: 编译生成的可执行二进制文件的 CPU 架构
- Object files: 每一个源代码文件编译后生成的对象文件列表
- Sections: 在最终可执行二进制文件中不同类型代码所在的节列表
- Symbols: 所有代码中的函数、变量、字符串等实际生成的符合列表
如何统计?
我们不用关心 Sections 中的内容,因为它只是对应到不同符号存储的位置,不影响它实际占用的体积。我们需要关注 Object files 和 Symbols 这两块内容,因为它实际包含了在可执行二进制文件中占有体积的部分。
其中 Object files 包含了每个代码文件编译出来的对象文件路径,以及它的编号。每个对象文件的编号,将在后续符号列表中被引用,从而标识那些符号是由哪个代码文件中的代码生成的。
例如上面示例中的 [ 1] /Users/ohdarling/Library/Developer/Xcode/DerivedData/LinkMapAnalyzer-atehkijmusbhcoahevnvjewudpxp/Build/Intermediates.noindex/LinkMapAnalyzer.build/Debug/FLMAApp.build/Objects-normal/x86_64/LMWindowController.o
,其中:
[ 1]
表示这个对象文件的编号为 1/Users/ohdarling/Library/....../LMWindowController.o
表示这个对象文件的路径LMWindowController.o
通常表示这个对象文件由 LMWindowController.m 生成
然后再在 Symbols 中,我们就可以分析到每个代码文件中的每个函数、变量等所占用的体积,以及它所在的对象文件,例如 0x100002CC0 0x00000080 [ 1] -[LMWindowController windowDidLoad]
:
0x100002CC0
表示这个函数在可执行二进制文件中的地址0x00000080
表示这个函数生成的指令代码所占的体积大小为 128 字节[ 1]
表示这个函数在编号为 1 的对象文件中-[LMWindowController windowDidLoad]
表示这个函数的签名为 LMWindowController 中的 windowDidLoad 方法
在符号列表中,我们就可以获取到每个类的每个函数的大小,再通过对象文件编号进行关联,就可以将每个代码文件所生成的指令在最终可执行二进制文件所占的体积大小统计出来。
生成统计报告
通过分析 Object files 和 Symbols,用对象文件编号将它们关联,并且通过对象文件与文件系统的文件名映射起来,最终我们就可以得到整个可执行二进制文件中,各个对象文件,以及各个对象文件中各个函数所占用的体积大小。
在得到分析结果之后,我们就可以明确知道,在整个项目中,是否有模块存在体积过大的问题,如果存在这个问题,那么就需要投入时间去尝试排查是否有冗余代码,或者废弃代码,从而降低最终可执行二进制文件的大小,给用户一个更好的下载体验。
结论
通过分析 Link Map 文件,我们可以精确地去计算可执行二进制文件中的各个函数、变量等所占的体积,从而可以针对性的对 iOS App 体积进行优化。
为了更快速、更直观的去分析 Link Map 生成的结果,我编写了一个 macOS 上的 FastLinkMapAnalyzer app,用于生成树状图分析结果,以及 Treemap 形式的图表,从而可以直观地通过图表的方式去观察到整个 App 中体积最大的模块。
FastLinkMapAnalyzer 可以在 Mac App Store 获取:https://apps.apple.com/app/fastlinkmapanalyzer/id1595283055?mt=12
希望此文对你有所帮助。
0 条评论。