iOS App 真实包大小:你以为的大小为什么是错的

6 阅读6分钟

核心结论:你在本地看到的 .ipa 大小,和用户在 App Store 实际下载的大小,可能差距超过 40%。

前言

每次发版前,你是否盯着 Xcode 给出的包大小报告,心里觉得"还好,没超标"?

但用户在 App Store 看到的下载大小,往往和你本地看到的完全不一样。

这篇文章会告诉你:

  • 为什么本地看到的大小不准
  • App Store 里的大小是怎么算出来的
  • 如何在本地提前得到接近真实的数值
  • 包大小由哪些部分构成,怎么针对性优化

一、先搞清楚:你在看哪个大小?

很多开发者混淆了这几个概念:

大小类型含义在哪里看
编译产物大小本地 .app 文件夹大小Finder
Archive 大小.ipa 文件大小Xcode Organizer
下载大小用户在 App Store 实际下载的大小App Store / TestFlight
安装大小安装到手机后占用的磁盘空间设置 → 通用 → iPhone 储存空间

大多数开发者盯着的是前两个,但用户感知的是下载大小

这四个数字,通常都不一样,而且差距可能很大。


二、App Store 对你的包做了什么?

你打包上传的 .ipa,到达用户手机之前,Apple 会做一系列处理。

2.1 App Thinning:按设备裁剪

你上传的 .ipa 包含了所有设备的资源和二进制:

MyApp.ipa
├── Binary(arm64 + x86_64 全架构)
├── 1x / 2x / 3x 图片资源
├── iPhone 专属资源
└── iPad 专属资源

但 iPhone 15 用户实际下载的只有:

用户实际下载
├── Binary(仅 arm64)
├── 3x 图片
└── iPhone 专属资源

其余全部被 Apple 裁掉。这一步通常能减少 30%~50% 的大小。

2.2 On-Demand Resources 不计入下载大小

如果你使用了 ODR(按需下载资源),这部分不会在首次下载时打包,**不计入下载大小 **,用到时才下载。

2.3 资源文件的二次处理

Apple 会对部分资源做再处理:

PNG       →  pngcrush 再压缩(通常变更小)
Plist     →  转成 binary plist(通常变小)
Storyboard → 编译成 nib

2.4 最终 ZIP 压缩

处理完之后,Apple 用 DEFLATE 对整个包重新压缩,压缩参数由 Apple 服务器控制。

整个流程如下:

你上传的 .ipa
    ↓ App Thinning(按设备裁剪)
    ↓ 剥离 ODR 资源
    ↓ 资源文件二次处理
    ↓ 重签名
    ↓ DEFLATE 压缩
    = 用户实际下载的大小

三、Xcode 的估算为什么也不准?

Xcode Archive 后会提供一份 App Size Report,展示各设备的估算大小。这个估算是本地模拟的,存在几个主要误差来源:

误差一:__TEXT 段压缩处理

Mach-O 二进制的 __TEXT 段(代码段)在 Apple 服务器端会做私有的布局优化和压 缩,本地无法完全复现,只能用经验系数近似估算。

误差二:重签名影响二进制布局

App Store 上传后 Apple 会重新签名,这会改变二进制部分结构,进而影响最终压缩率。

误差三:编译工具链差异

Apple 服务器的编译工具链版本可能与本地不一致,在开启 Bitcode 的历史版本中差异尤为明显。

💡 结论:Xcode 报告的大小仅供参考,误差可能在 5%~15% 之间。


四、如何得到接近真实的包大小?

方法一:TestFlight(最准确)

上传后在 App Store Connect 后台可以看到各设备的真实下载大小。

优点: 走了 Apple 完整处理流程,最准确。 缺点: 需要先上传,无法在开发阶段提前预知。

方法二:手动模拟 App Thinning

# 1. 提取 arm64 单架构 binary
lipo MyApp.app/MyApp -extract arm64 -output MyApp_arm64

# 2. 查看各 section 大小
size -m MyApp_arm64

# 3. 重新打包压缩,估算下载大小
zip -r MyApp_thinned.zip MyApp.app

优点: 快速,无需上传。 缺点: 未考虑资源裁剪,误差相对较大。

方法三:linkmap 归因分析(推荐)

在 Xcode Build Settings 开启:

WRITE_LINK_MAP_FILE = YES

编译后生成 linkmap 文件,记录了每个符号的大小和所属模块:

# Object files
[  1] /path/to/MyModule.o
[  2] /path/to/Pods/Alamofire.o

# Symbols
# Address      Size       File    Name
0x100001000   0x000001A0  [ 1]   -[MyViewController viewDidLoad]
0x100001200   0x00000080  [ 2]   _Alamofire_request

解析这个文件,可以精确知道每个库、每个类占了多少二进制大小,帮助定位膨胀来 源。


五、包大小由哪些部分构成?

下载大小
├── 二进制(通常占 40%~60%)
│   ├── 业务代码
│   ├── 三方库(Pods / SPM)
│   └── Swift 标准库(旧系统版本需要内嵌)
├── 资源文件(图片、音频、字体)
├── Frameworks(动态库)
└── 其他(Storyboard、Plist、配置文件)

根据经验,大多数 app 包大小增长的主要来源是:

  1. 三方库无节制累积
  2. 图片资源未压缩/未清理
  3. 内嵌了不必要的字体文件

六、针对性优化方向

二进制瘦身

# 查看未使用代码(Dead Code Stripping 默认开启,确认一下)
# Build Settings → Dead Code Stripping = YES

# Swift 编译优化
# Build Settings → Swift Optimization Level = Optimize for Speed [-O]
  • 定期审查并移除不再使用的三方库
  • 合并功能重叠的库
  • 使用 periphery 扫描未使用的代码

资源文件优化

  • 使用 WebP 替代 PNG/JPEG,体积可减少 25%~35%
  • 用 Asset Catalog 管理图片,配合 On-Demand Resources
  • 使用 FengNiao 扫描并删除未引用的图片

字体优化

  • 只内嵌实际用到的字重
  • 使用系统字体替代自定义字体(San Francisco 系列无需内嵌)

七、接入 CI 监控,让大小可见

单次优化效果有限,更重要的是防止包大小悄悄增长

建议在 CI 流程中加入大小检查:

# 示例:GitHub Actions 中检查 linkmap
- name: Check App Size
  run: |
    python3 scripts/parse_linkmap.py \
      --linkmap build/MyApp-LinkMap.txt \
      --threshold 50MB \
      --fail-on-exceed

每次 PR 都能看到大小变化,问题在合入前就能发现。


总结

说明
本地 .ipa 大小不准,包含多架构和全量资源
Xcode App Size Report估算值,有一定误差
TestFlight 后台数据最准确,需上传后查看
linkmap 分析精确归因,找到膨胀来源

推荐的工作流:

日常开发用 linkmap 监控增量 → 发版前用 TestFlight 确认真实大小 → CI 集成大小检测防止劣化


包大小看似是个小问题,但研究表明包大小每增加 6MB,下载转化率下降约 1%。在竞争激烈的 App Store,这是值得持续关注的指标。

如果这篇文章对你有帮助,欢迎点赞收藏 🙏,有问题欢迎在评论区讨论。


参考资料