在进行包体积优化之前,首先需要了解如何分析包体积。本文介绍常用的分析工具和Mach-O文件结构。


查看IPA构成

解压IPA文件分析各部分大小:

# 解压IPA
unzip -q YourApp.ipa -d ./ipa_content

# 查看各文件大小
find ./ipa_content -type f -exec ls -lh {} \; | sort -k5 -h -r | head -20

LinkMap分析

LinkMap文件记录了链接器生成可执行文件的详细信息,可以精确分析二进制大小:

# Build Settings中设置
Write Link Map File = YES
Path to Link Map File = $(TARGET_TEMP_DIR)/$(PRODUCT_NAME)-LinkMap-$(CURRENT_VARIANT)-$(CURRENT_ARCH).txt

LinkMap文件结构

# Path: /path/to/YourApp
# Arch: arm64

# Object files:
[  0] linker synthesized
[  1] /path/to/ViewController.o
[  2] /path/to/Model.o

# Sections:
# Address   Size        Segment Section
0x100001000 0x00012345  __TEXT  __text
0x100013345 0x00001234  __TEXT  __stubs

# Symbols:
# Address   Size        File  Name
0x100001000 0x00000100  [  1] -[ViewController viewDidLoad]
0x100001100 0x00000050  [  2] -[Model init]

解析脚本

可以使用脚本解析LinkMap统计各模块大小:

import re
from collections import defaultdict

def parse_linkmap(filepath):
    object_files = {}
    symbols_size = defaultdict(int)
    
    with open(filepath, 'r') as f:
        content = f.read()
    
    # 解析Object files
    obj_pattern = r'\[\s*(\d+)\]\s+(.+)'
    for match in re.finditer(obj_pattern, content):
        index = int(match.group(1))
        path = match.group(2)
        object_files[index] = path
    
    # 解析Symbols
    sym_pattern = r'0x[0-9A-Fa-f]+\s+(0x[0-9A-Fa-f]+)\s+\[\s*(\d+)\]'
    for match in re.finditer(sym_pattern, content):
        size = int(match.group(1), 16)
        file_index = int(match.group(2))
        if file_index in object_files:
            symbols_size[object_files[file_index]] += size
    
    # 按大小排序输出
    sorted_sizes = sorted(symbols_size.items(), key=lambda x: x[1], reverse=True)
    for path, size in sorted_sizes[:20]:
        print(f"{size/1024:.2f} KB - {path}")

parse_linkmap('YourApp-LinkMap.txt')

第三方工具

常用工具

  • Emerge Tools:提供包体积分析和优化建议
  • bloaty:Google开源的二进制分析工具
  • size:macOS自带的二进制大小分析工具
# 使用size命令
size -m YourApp.app/YourApp

WBBlades

WBBlades 是58同城开源的基于Mach-O的综合分析工具,提供多种包体积分析能力。

主要功能

功能说明
无用类检测支持ObjC和Swift,分析Mach-O中的类引用关系
无用协议检测检测未被实现或引用的协议
无用资源检测检测未使用的图片等资源文件
包大小分析分析各模块对包体积的贡献
崩溃解析支持dSYM符号化和GPT辅助分析

工作原理

WBBlades通过解析Mach-O文件结构,分析以下关键Section:

__DATA.__objc_classlist  → 所有定义的ObjC类
__DATA.__objc_classrefs  → 所有被引用的ObjC类
__DATA.__objc_selrefs    → 所有被引用的方法选择器
__DATA.__objc_protolist  → 所有定义的协议

通过对比定义和引用,找出未使用的类、方法和协议。

特点

  • 支持ObjC和Swift混编项目
  • 提供GUI应用和命令行工具
  • 无需源码,直接分析二进制文件
  • 支持静态库(.a)和动态库(.dylib/.framework)分析

APPAnalyze

APPAnalyze 是京东云技术团队开发的iOS应用包分析工具。

主要功能

功能说明
未使用类检测支持ObjC,Swift需开启配置
未使用方法检测检测ObjC未使用的方法
未使用属性检测检测ObjC未使用的属性
未使用资源检测检测未使用的imageset和其他资源
大资源检测检测超过指定大小的资源文件
+load方法检测检测调用+load方法的类
包体积数据生成生成各模块的包体积数据报告

使用方式

# 基础使用
/Users/Test/APPAnalyzeCommand -ipa /path/to/YourApp.app --output /path/to/output

# 使用配置文件
/Users/Test/APPAnalyzeCommand -ipa /path/to/YourApp.app -config /path/to/config.json --output /path/to/output

特点

  • 支持组件化工程扫描,细化到每个模块的包体积问题
  • 可配置的检测规则,支持过滤和自定义
  • 误报率低,扫描质量高
  • 可集成到CI流水线自动发现问题

MachOView

MachOView 是一款用于查看和分析Mach-O文件结构的可视化工具。

主要功能

  • 可视化展示Mach-O文件的完整结构
  • 查看Header、Load Commands、Segments、Sections等
  • 分析符号表、字符串表等内容
  • 帮助开发者深入理解二进制文件组成

Asset Catalog Tinkerer

Asset Catalog Tinkerer 用于查看和提取Xcode资产目录(Assets.car)内容。

主要功能

  • 查看Assets.car中的所有资源
  • 提取图片资源
  • 分析资源占用情况
  • 帮助识别冗余或过大的资源

bloaty

bloaty 是Google开源的二进制文件大小分析工具。

主要功能

  • 分析二进制文件各部分的大小
  • 支持多种二进制格式(包括Mach-O)
  • 提供详细的大小分解报告
  • 支持对比两个版本的大小差异
# 安装
brew install bloaty

# 分析Mach-O文件
bloaty YourApp.app/YourApp

# 对比两个版本
bloaty new_version -- old_version

Emerge Tools

Emerge Tools 是一个商业化的包体积分析平台,提供:

  • 自动化包体积分析
  • 历史趋势追踪
  • PR集成,检测每次提交的体积变化
  • 优化建议

Sentry Size Analysis(Beta)

Sentry Size Analysis 是Sentry提供的包大小分析功能(目前为Beta版本),帮助在预发布阶段监控应用体积,防止意外的体积增长影响用户。

主要功能

功能说明
包大小监控追踪每次构建的安装大小变化
CI集成支持Fastlane插件和Sentry CLI上传
版本对比自动对比当前构建与基准构建的差异
Insights优化建议提供具体的优化方向和预估节省空间

支持的上传格式

  • XCArchive(推荐)
  • IPA

Insights优化建议

Sentry的Insights功能会自动分析包内容并提供优化建议:

Insight类型检测内容
Strip Debug Symbols检测可移除的调试符号
Duplicate Files检测重复文件
Image Optimization检测可优化的PNG/JPEG/HEIC图片
Alternate Icon Optimization检测可优化的备用图标
Loose Images检测应移入Asset Catalog的散落图片
String Localization检测可压缩的本地化字符串
Main Binary Export Metadata检测可移除的导出符号信息

Mach-O文件结构

理解Mach-O文件结构是进行二进制优化的基础。Mach-O(Mach Object)是macOS/iOS上的可执行文件格式。

更详细的Mach-O介绍请参考:Mach-O的链接、装载与库

基本结构

┌─────────────────────────────┐
│         Header              │  ← 文件类型、CPU架构、Load Commands数量
├─────────────────────────────┤
│      Load Commands          │  ← 描述各Segment的位置和大小
├─────────────────────────────┤
│       __PAGEZERO            │  ← 空指针陷阱区(不占文件空间)
├─────────────────────────────┤
│         __TEXT              │  ← 代码段(只读、可执行)
│    ├── __text               │     机器码(ObjC和Swift)
│    ├── __stubs              │     符号桩
│    ├── __stub_helper        │     桩辅助代码
│    ├── __cstring            │     C字符串常量
│    ├── __objc_methname      │     ObjC方法名
│    ├── __swift5_typeref     │     Swift类型引用字符串
│    └── __swift5_reflstr     │     Swift反射字符串
├─────────────────────────────┤
│      __DATA_CONST           │  ← 运行时常量(iOS 13+)
│    ├── __got                │     非延迟绑定符号指针
│    ├── __objc_classlist     │     ObjC类列表
│    ├── __objc_protolist     │     ObjC协议列表
│    ├── __swift5_proto       │     Swift协议描述符
│    └── __swift5_types       │     Swift类型描述符
├─────────────────────────────┤
│         __DATA              │  ← 数据段(可读写)
│    ├── __la_symbol_ptr      │     延迟绑定符号指针
│    ├── __objc_classrefs     │     ObjC类引用
│    ├── __objc_selrefs       │     ObjC方法选择器引用
│    ├── __data               │     初始化的全局变量
│    └── __swift5_fieldmd     │     Swift字段元数据
├─────────────────────────────┤
│       __LINKEDIT            │  ← 链接信息
│    ├── Symbol Table         │     符号表
│    ├── String Table         │     字符串表
│    └── Code Signature       │     代码签名
└─────────────────────────────┘

iOS 13+的__DATA段拆分

iOS 13之前,__DATA段混合了"启动后不再修改的数据"和"运行时会修改的数据"。iOS 13+将其拆分为:

  • __DATA_CONST:启动时写入,之后变为只读,可在多进程间共享
  • __DATA:运行时可能修改的数据
  • __DATA_DIRTY:一定会被写入的数据,单独分页以优化COW

各段对包体积的影响

ObjC相关Section

Segment/Section内容优化方向
__TEXT.__text编译后的机器码编译器优化、删除无用代码
__TEXT.__cstringC字符串常量减少字符串使用、去重
__TEXT.__objc_methnameObjC方法名减少方法数量
__DATA.__objc_classlistObjC类定义减少类数量
__DATA.__objc_constObjC常量(方法列表等)减少方法、属性数量
__LINKEDIT符号表、签名Strip符号

Swift相关Section

Swift 5引入了稳定的ABI,其元数据存储在以__swift5_开头的Section中:

Segment/Section内容说明
__TEXT.__swift5_typeref类型引用字符串类型名称的mangled字符串
__TEXT.__swift5_reflstr反射字符串属性名、枚举case名等
__DATA_CONST.__swift5_proto协议描述符Swift协议的元数据
__DATA_CONST.__swift5_types类型描述符struct/class/enum的元数据
__DATA.__swift5_fieldmd字段元数据属性的类型和偏移信息
__DATA.__swift5_assocty关联类型协议关联类型信息
__DATA.__swift5_protos协议一致性类型遵循协议的记录

Swift元数据的体积影响

Swift的反射和泛型特性需要大量元数据支持,这些Section可能占用较多空间。优化方向:

  • 减少public类型和属性(减少导出的元数据)
  • 使用@frozen标记不会改变的struct(减少运行时元数据)
  • 避免过度使用泛型(减少类型特化产生的代码膨胀)

查看Mach-O文件类型

# 查看文件类型
$ file YourApp.app/YourApp
Mach-O 64-bit executable arm64

# 常见的Mach-O类型
MH_EXECUTE    # 可执行文件(App的主二进制)
MH_DYLIB      # 动态库(.dylib, .framework)
MH_OBJECT     # 目标文件(.o,静态库中的单元)
MH_BUNDLE     # Bundle(插件)

查看各段大小

可以使用size命令查看各段大小:

size -l -m YourApp.app/YourApp

# 输出示例:
# Segment __TEXT: 12582912 (vmaddr 0x100000000 fileoff 0)
#     Section __text: 8234567
#     Section __stubs: 12345
#     Section __objc_methname: 234567
# Segment __DATA: 2097152
#     Section __objc_classlist: 12345
#     ...

使用otool查看详细信息

# 查看Mach-O头信息
otool -h YourApp

# 查看Load Commands
otool -l YourApp

# 查看ObjC类列表
otool -v -s __DATA __objc_classlist YourApp

# 查看ObjC类引用
otool -v -s __DATA __objc_classrefs YourApp

# 查看selector引用
otool -v -s __DATA __objc_selrefs YourApp

使用nm查看符号

# 查看所有符号
nm YourApp

# 只查看未定义符号(外部依赖)
nm -U YourApp

# 查看已定义符号(排除未定义项)
nm YourApp | grep -v " U "

# 查看ObjC类符号
nm YourApp | grep "_OBJC_CLASS_\\$" | grep -v " U "