iOS App 包大小组成:为什么它会大

6 阅读11分钟

背景

一个 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,如 textconstcstringunwind_infoobjc_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 二进制

  • 其中的 TEXTDATALINKEDIT 等 section

  • 可能附带资源、配置文件、隐私描述或桥接代码

这些 Framework 往往来自哪里

  • 支付、登录、客服、地图、埋点、监控、社交分享等三方 SDK

  • 业务拆分出来的动态模块

  • 组件化架构下单独打包的功能模块

为什么 Framework 会大

Framework 体积通常由这些因素决定:

  • 自带大量代码和依赖

  • 附带资源文件

  • 含有较多运行时元数据

  • 一个能力拆分为多个 Framework,一起被打包

  • 某些 SDK 既有主框架又有配套资源 bundle

如果某几个 Framework 占比特别高,往往说明它们已经是包体治理的重点对象。

3. StringsCFStrings

这类区域通常会在图里单独出现,而且有时面积非常大。

它由哪些内容构成

主要包括:

  • 源码里的字符串字面量

  • 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

  • 一些装载和链接辅助信息

这一层通常不是首要优化对象,但会随着前两层的膨胀而同步变大。

哪些部分最值得优先关注

一般来说,优化优先级可以这样看:

  1. 主二进制 Mach-O

   重点看 __TEXT__DATA_CONST、大字符串、元数据和未裁剪代码。

  1. 大型 Frameworks

   重点看三方 SDK、重复能力、动态模块是否过多。

  1. Assets.car.bundle

   重点看图片、活动资源、重复素材和未清理资源。

  1. Strings / CFStrings

   重点看埋点 JSON、模板、路由、日志、硬编码常量。

  1. Videos / Fonts / CoreML Models

   这类通常单项就很大,适合专项治理。

结论

iOS 包大小并不是一个单独数字,而是由“代码 + 资源 + 链接装载信息 + 签名信息”共同构成。

如果只做通用介绍,不看具体项目名,那么最重要的理解是:

  • Mach-O 代表代码本体和运行时元数据

  • Frameworks 代表嵌入式模块和三方 SDK 成本

  • Strings 代表被编译进二进制的文本常量

  • Assets / bundles / Localizations / Fonts / Videos / Models 代表资源层成本

  • Code SignatureDYLD 代表打包与装载的附加成本

真正做包体治理时,通常不是所有部分都同时优化,而是先找面积最大的几块,逐项确认“它是由什么组成的”,再决定是删代码、减 SDK、压资源,还是挪字符串。