核心结论:你在本地看到的
.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 包大小增长的主要来源是:
- 三方库无节制累积
- 图片资源未压缩/未清理
- 内嵌了不必要的字体文件
六、针对性优化方向
二进制瘦身
# 查看未使用代码(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,这是值得持续关注的指标。
如果这篇文章对你有帮助,欢迎点赞收藏 🙏,有问题欢迎在评论区讨论。
参考资料
- [Apple 官方文档:Reducing Your App's Size](developer.apple.com/docum entation/xcode/reducing-your-app-s-size)
- WWDC 2019: Optimizing App Launch
- [Mach-O Programming Topics](developer.apple.com/library/arc… mentation/Performance/Conceptual/CodeFootprint/Articles/MachOOverview.html)