背景
一个 iOS 安装包并不只是“一个 App 可执行文件”,而是由代码、动态库、资源、签名信息和链接装载相关数据共同构成的。
如果不看具体业务模块名称,只从包体组成的角度来理解,一般可以把 iOS 包大小拆成下面几大部分:
-
主二进制
Mach-O -
三方或系统集成的
Frameworks -
字符串常量
Strings / CFStrings -
资源文件
Asset Catalogs / bundles -
本地化资源
Localizations -
字体
Fonts -
视频
Videos -
模型文件
CoreML Models -
签名信息
Code Signature -
动态链接相关信息
DYLD
下面按这些大类分别解释它们是什么、由哪些内容构成,以及为什么会变大。
1. 主二进制 Mach-O
这是 iOS 包体里最核心的一部分,通常也是最大的部分之一。
它本质上就是 App 的可执行文件,包含程序真正要运行的代码和运行时元信息。
它由哪些内容构成
常见会看到这些 section:
-
__TEXT -
__DATA_CONST -
__DATA -
__LINKEDIT -
以及一些更细的子 section,如
text、const、cstring、unwind_info、objc_stubs
每一项的含义
__TEXT
这是最值得关注的一块,属于只读代码区,常见由以下内容组成:
-
编译后的机器码,也就是实际执行逻辑,对应常见的
__text -
字符串常量,比如埋点名、路由、JSON 模板、日志文案等
-
Objective-C 方法名、类名、协议名
-
Swift 类型信息、协议描述、反射元数据
-
一些编译器生成的只读常量
如果 __TEXT 很大,通常意味着:
-
代码量本身很多
-
字符串硬编码很多
-
Swift/ObjC 元数据很多
-
引入了较大的功能模块或 SDK
__DATA_CONST
这是“运行时需要、但尽量保持只读”的数据区,常见由这些内容构成:
-
常量对象
-
类/协议相关引用表
-
Objective-C / Swift 的一部分运行时结构
-
只初始化一次、后续基本不改的数据
这一块大,往往说明:
-
常量表很多
-
类型和运行时元信息多
-
静态配置或映射表较多
__DATA
这是可写数据区,通常包含:
-
全局变量
-
静态变量
-
懒加载后需要修改的运行时数据
-
一些运行期状态表
它一般不会像 __TEXT 那样巨大,但如果全局缓存、静态对象或大表很多,也会变大。
unwind_info / gcc_except_tab
这类区域主要和异常处理、栈回溯有关,通常由以下内容构成:
-
崩溃时栈展开信息
-
异常处理元数据
-
调试和运行时回溯需要的辅助表
它们通常不是最大的项,但在 C++、ObjC 异常、复杂调用链较多时会有一定占用。
objc_stubs
这部分与 Objective-C 动态派发有关,通常由:
-
方法调用跳板
-
运行时消息发送相关辅助结构
如果 ObjC 代码和运行时调用很多,这一块会更明显。
为什么主二进制会变大
最常见的原因有:
-
业务代码过多
-
历史废弃功能未清理
-
大量三方 SDK 直接参与链接
-
字符串、JSON、模板硬编码进源码
-
Swift 泛型、协议、反射元数据膨胀
-
编译优化和裁剪配置不充分
2. Frameworks
这是包体里第二类非常重要的组成部分。
它表示 App 随包携带的动态 Framework 或嵌入式二进制模块。
它由哪些内容构成
每个 Framework 通常都会包含:
-
一个自己的
Mach-O二进制 -
其中的
TEXT、DATA、LINKEDIT等 section -
可能附带资源、配置文件、隐私描述或桥接代码
这些 Framework 往往来自哪里
-
支付、登录、客服、地图、埋点、监控、社交分享等三方 SDK
-
业务拆分出来的动态模块
-
组件化架构下单独打包的功能模块
为什么 Framework 会大
Framework 体积通常由这些因素决定:
-
自带大量代码和依赖
-
附带资源文件
-
含有较多运行时元数据
-
一个能力拆分为多个 Framework,一起被打包
-
某些 SDK 既有主框架又有配套资源 bundle
如果某几个 Framework 占比特别高,往往说明它们已经是包体治理的重点对象。
3. Strings 与 CFStrings
这类区域通常会在图里单独出现,而且有时面积非常大。
它由哪些内容构成
主要包括:
-
源码里的字符串字面量
-
Objective-C 的
CFString常量 -
埋点事件名、参数名
-
接口字段名
-
路由名、页面名
-
日志文案
-
兜底文案
-
JSON / HTML / SQL 模板字符串
为什么它会大
这类问题在业务工程里非常常见,原因通常是:
-
大段 JSON 直接写在代码里
-
埋点配置采用硬编码
-
大量重复字符串没有抽取复用
-
多语言文本或模板被直接编译进二进制
它和资源文件的区别
字符串常量一旦直接出现在代码中,通常会进入二进制的只读区域;
而如果把内容放进 .json、.plist、.strings 或其他资源文件,往往会从主二进制中挪出去,转移到资源包占用。
4. Code Signature
这是 iOS 安装包里必须存在的一部分,用于代码签名校验。
它由哪些内容构成
通常包括:
-
可执行文件签名信息
-
各嵌入式 Framework 的签名信息
-
资源摘要
-
与代码完整性校验有关的数据
为什么它会变大
它通常会随着以下内容同步增长:
-
App 包含的文件数量变多
-
嵌入式 Framework 变多
-
资源文件变多
-
整个包体本身变大
它不是最容易优化的区域,因为它更像是“结果项”,并不是业务可直接删减的内容。
通常只有当主二进制和资源整体变小时,签名体积才会跟着下降。
5. DYLD
这部分和动态链接器有关,主要是 App 在启动时如何把各个二进制模块装载、重定位、绑定。
它由哪些内容构成
常见包括:
-
Rebase -
Bind -
Lazy Bind -
Export -
其他符号装载、重定位相关表
每一项大致代表什么
Rebase
表示二进制在装载到内存时,需要修正地址引用的相关信息。
Bind
表示符号需要和外部依赖绑定的相关信息,比如链接到系统库、其他 Framework 的符号。
Lazy Bind
表示延迟绑定的信息,也就是某些符号在真正调用时再绑定。
Export
表示当前二进制对外暴露的符号信息。
为什么它会变大
通常与以下因素有关:
-
动态库数量多
-
符号数量多
-
模块之间引用关系复杂
-
Objective-C / Swift 元数据和导出符号较多
这部分一般不会是包体最大的元凶,但能反映出工程的链接复杂度。
6. 资源包 bundles / Asset Catalogs
除了代码本身,资源通常是包体里第二大来源。
在 X-Ray 里,这部分经常以 Assets.car、.bundle、图片资源集合的形式出现。
它由哪些内容构成
常见包括:
-
Assets.car -
各功能模块的
.bundle -
PNG / JPG / WebP 图片
-
图标、启动图、占位图
-
动画资源
-
颜色集、符号资源
-
一些打包进去的模板文件、配置文件
为什么它会大
常见原因有:
-
图片分辨率过高
-
同类资源多份重复
-
历史活动资源未清理
-
多个模块各自带一套相似素材
-
资源没有压缩或没有统一治理
Assets.car 是什么
它是 Xcode 把 Asset Catalog 编译后的产物。
其中可能包含:
-
图片
-
颜色
-
App Icon
-
Launch 相关资源
-
深浅色模式变体
-
不同设备密度版本
所以一个大的 Assets.car,通常意味着你的图片和视觉资源总体很大。
7. 本地化资源 Localizations
这部分表示多语言相关内容。
它由哪些内容构成
通常包括:
-
.strings -
.stringsdict -
不同语言目录下的文案文件
-
某些多语言图片或多语言资源副本
为什么它会变大
可能的原因有:
-
支持语言很多
-
文案量很大
-
某些资源按语言重复打包
-
历史语言包没有清理
多语言通常是合理成本,但也值得检查是否存在无效语言、重复文案或冗余资源。
8. Fonts
字体在一些内容型、品牌型应用里会占据不小的空间。
它由哪些内容构成
常见包括:
-
.ttf -
.otf -
多字重字体文件
-
不同语种字体文件
为什么它会大
最常见的原因是:
-
引入了整套字体家族
-
一个品牌字体带了多个字重
-
同时支持拉丁、中文、阿拉伯等大字符集字体
字体文件单个就可能比较大,多个一起打包时增长会很明显。
9. Videos
如果 App 内置了宣传视频、引导视频、动画素材,这一块会直接抬高安装包大小。
它由哪些内容构成
通常包括:
-
mp4 -
mov -
启动引导视频
-
营销素材
-
本地离线播放内容
为什么它会大
因为视频天然就是大文件。
如果不是强依赖离线播放,通常不建议把大视频直接打进安装包。
10. CoreML Models
如果 App 带有端上机器学习能力,这部分会出现在包体分析里。
它由哪些内容构成
通常包括:
-
.mlmodelc -
模型权重文件
-
配套标签、配置和预处理资源
为什么它会大
模型文件本身就可能非常大,尤其是:
-
图像识别模型
-
推荐排序模型
-
OCR / NLP 模型
-
多任务模型
这类内容通常是“功能换体积”,需要单独评估是否值得内置。
11. Unmapped
在一些包体拆解结果里,还会出现 Unmapped 这类项。
它代表什么
它通常表示:
-
工具暂时无法精确归类的内容
-
某些中间格式或特殊 section
-
统计时未映射到预设类别的文件或字节
应该怎么理解
它不是一个具体文件类型,更像是“暂未细分归属”的集合。
如果占比很小,可以先忽略;如果占比明显,就值得进一步下钻看具体文件明细。
从整体上看,iOS 包大小通常是怎么构成的
如果按治理视角来归纳,一个 iOS 安装包通常可以粗分为三大层:
第一层:代码层
对应:
-
主二进制
Mach-O -
各类
Frameworks -
DYLD -
一部分字符串和运行时元数据
这一层决定了:
-
代码体积
-
模块数量
-
启动装载复杂度
-
三方 SDK 负担
第二层:资源层
对应:
-
Assets.car -
.bundle -
图片
-
本地化资源
-
字体
-
视频
-
模型文件
这一层决定了:
-
图片和视觉资源体积
-
多语言成本
-
多媒体资源成本
-
离线能力和端侧模型成本
第三层:打包与签名层
对应:
-
Code Signature -
一些装载和链接辅助信息
这一层通常不是首要优化对象,但会随着前两层的膨胀而同步变大。
哪些部分最值得优先关注
一般来说,优化优先级可以这样看:
- 主二进制
Mach-O
重点看 __TEXT、__DATA_CONST、大字符串、元数据和未裁剪代码。
- 大型
Frameworks
重点看三方 SDK、重复能力、动态模块是否过多。
Assets.car和.bundle
重点看图片、活动资源、重复素材和未清理资源。
Strings / CFStrings
重点看埋点 JSON、模板、路由、日志、硬编码常量。
Videos / Fonts / CoreML Models
这类通常单项就很大,适合专项治理。
结论
iOS 包大小并不是一个单独数字,而是由“代码 + 资源 + 链接装载信息 + 签名信息”共同构成。
如果只做通用介绍,不看具体项目名,那么最重要的理解是:
-
Mach-O代表代码本体和运行时元数据 -
Frameworks代表嵌入式模块和三方 SDK 成本 -
Strings代表被编译进二进制的文本常量 -
Assets / bundles / Localizations / Fonts / Videos / Models代表资源层成本 -
Code Signature和DYLD代表打包与装载的附加成本
真正做包体治理时,通常不是所有部分都同时优化,而是先找面积最大的几块,逐项确认“它是由什么组成的”,再决定是删代码、减 SDK、压资源,还是挪字符串。