库体积优化

概念

符号表

符号表是内存地址与函数名、文件名、行号的映射表。符号表元素如下所示:

<起始地址> <结束地址> <函数> [<文件名:行号>]

Debug Symbol 调试符号

借助符号调试程序可以将原始符号表转换成可读性较强的符号表,主要是方便开发人员获取调试信息

还原前:

1
2
3
4
5
Thread 0 Crashed:
0 libobjc.A.dylib 0×300c87ec 0×300bb000 + 55276
1 MobileLines 0×00006434 0×1000 + 21556
2 MobileLines 0×000064c2 0×1000 + 21698
3 UIKit 0×30a740ac 0×30a54000 + 131244

还原后:

1
2
3
4
5
Thread 0 Crashed:
0 libobjc.A.dylib 0×300c87ec objc_msgSend + 20
1 MobileLines 0×00006434 -[BoardView setSelectedPiece:] (BoardView.m:321)
2 MobileLines 0×000064c2 -[BoardView touchesBegan:withEvent:] (BoardView.m:349)
3 UIKit 0×30a740ac -[UIWindow sendEvent:] + 264

DWARF

一种支持源代码级别调试的调试文件格式

DWARF是平台独立的且适用于任何处理器任何操作系统

dSYM

iOS平台中,dSYM文件是指具有调试信息的目标文件,文件名通常为:xxx.app.dSYM

为了避免进行 Strip 操作后调试符号的丢失,你可以使用dwarf-with-dsym选项。

DWARF with dSYM 选项在标准的DWARF之外执行一个额外的步骤:创建一个单独的MyApp.app.dSYM文件,这个文件包含你的程序的所有调试符号(这个文件其实是一个包,可以通过右键->显示包内容进行查看)。

事实上,DWARF with dSYM选项允许你对你进行单步调试而不管可执行程序是否被剥离了调试信息(stripped)。

这是可能的,这是因为gdb将会在你的程序的目录下查找.dSYM文件。它不需要知道对象文件(object files)的名字或者路径。如果你不除去调试符号 (strip debugging symbols),你可以使用.o或者.dSYM文件来调试。

DWARF - Object files and linked products will use DWARF as the debug information format. [dwarf]

DWARF with dSYM File - Object files and linked products will use DWARF as the debug information format, and Xcode will also produce a dSYM file containing the debug information from the individual object files (except that a dSYM file is not needed and will not be created for static library or object file products). [dwarf-with-dsym]

静态库无法产生 dsym

编译选项

Generate Debug Symbols

是否产生调试符号

当Generate Debug Symbols设置为YES时,编译产生的.o文件会大一些,当然最终生成的可执行文件也大一些(亲测如此)

当Generate Debug Symbols设置为NO的时候,在Xcode中设置的断点不会中断。但是在程序中打印 [NSThread callStackSymbols],依然可以看到类名和方法名

在程序崩溃时,也可以得到带有类名和方法名的函数调用栈

1
9   QQLiveInternational 0x00000001069c2515 -[QVNUICustomView layoutSubviews] + 3253

结论:Debug/Release 均打开

Debug Information Level

调试符号携带的信息等级

Debug Information Level 默认值为 Compiler default,另一个选项是 Line tables only

Generate Debug Symbols = YES 的前提下,如果选择 Line tables only,调试信息带有函数名、文件名和行号,但是不包含其他数据(比如局部变量和函数参数)。所以断点可以工作,但是无法在 lldb 中查看局部变量的值(亲测如此)

Deployment Postprocessing

该选项是所有 Strip 选项的总开关

Strip,也就是把生成的 .o 文件中剥离特定的符号(symbols)

Strip Linked Product

  • 如果 Deployment Postprocessing 不打开,该选项没有作用
  • 如果该选项生效,app 构建的过程多了两步
    1. 在app构建的开始,会生成一些.hmap辅助文件
    2. 在app构建的末尾,会执行Strip操作
  • 如果该选项生效,断点不会中断,在程序中打印 [NSThread callStackSymbols] 也无法看到类名和方法名,程序崩溃时无法看到类名和方法名

Strip Style

Strip Linked Product 打开时该选项才生效(待验证)

剥离符号的程度依次是All Symbols > Non-Global Symbols > Debugging Symbols

  • All Symbols:剥离所有符号表和重定向信息,适用于最后生成 .app 的工程
  • Non-Global Symbols:剥离非全局的符号(包括调试符号),保留外部符号,适用于 bundle 和 framework
  • Debugging Symbols:剥离调试符号,保留局部符号和全局符号,都适用

Strip Linked Product = YES 时,如果把 Strip StyleAll Symbols 改为 Debugging Symbols,则程序崩溃时就可以看到类名和方法名

注意如果构建的是一个静态库,则不可以 strip all,因为剥离了所有符号的静态库是无法被正常链接的(亲测编译失败)

优点:

  • 将一些函数內联化
  • 去除了一些无用代码
  • 对程序有全局的优化作用

缺点:

  • 降低编译链接速度,只建议在打正式包时开启
  • 降低 link map 可读性(多出了很多数字开头的“类”)

我是如何把 600M 的库减少到 30M

1
2
3
Deployment Postprocessing = 1
Strip Linked Product = 1
Strip Style = Non-Global Symbols

不过这样会导致 crash 时无法查看堆栈信息

参考文章

注意

  • 是对 Framrwork 的 Target 进行设置,而不是用于打包的 Target
  • Generate Debug Symbols = NO,Deployment Postprocessing = NO时,体积仍然很大(猜测:strip 不止剥离了 debug symbols,还剥离了其他内容,比如符号表)
  1. 概念
    1. 符号表
    2. Debug Symbol 调试符号
    3. DWARF
    4. dSYM
  2. 编译选项
    1. Generate Debug Symbols
    2. Debug Information Level
    3. Deployment Postprocessing
    4. Strip Linked Product
    5. Strip Style
    6. Link Time Optimization
  3. 我是如何把 600M 的库减少到 30M
  4. 参考文章
  5. 注意