iOS包体积优化实践
作者丨Augus
来源丨搜狐技术产品(ID:sohu-tech)
本文字数:7972字
预计阅读时间:20分钟
❝用最通俗的语言,描述最难懂的技术
前情描述
最近领导给我分配了包体积监控指标,恰好之前我也做过相关包体积的优化,于是便综合之前的实践经验,对此我们的项目的包体积优化做一个整体的记录,希望可以帮助有需要的小伙伴
iOS(安装)包
ipa
全称为iPhone Application Archive
,这里指的包在iOS
其实就是安装包,直观展示就是xxx.ipa
文件
iOS包内容
想要对其优化,就要知道包里面都有什么,知道了有什么,针对不同类型的文件,做不同的处理,从而达到一定的优化
我们首先找到一个xxx.ipa
文件,然后把后缀改为xxx.zip
,然后进行解压缩操作,如下图所示
然后右键选中显示包内容
,里面就是安装包包含的所有资源,大概可以分为以下几类
_CodeSignature
文件夹:ipa
包签名文件的存放文件夹Assets.car
:Assets.xcassts
在编译过程中生成的最终展示文件,默认里面存放各种分辨率图片(测试项目未使用)embedded.mobileprovision
:证书配置文件Info.plist
:项目配置表Plugins
:App
创建的扩展,比如Widget
、Push
和Share
等.Iproj
:App
所支持的语言文件exec
文件:可执行文件,例如widgetExtension
图片资源: .png,.jpg,.webp,.gif
其它资源文件 .xml,.json
.plist
:项目中使用资源的.plist
文件.bundle
:Mac OS
下的资源包集合.conf
:相关的配置文件.cer
,.der
,.p12
:钥匙串文件.wav
:音频文件.js
,.html
.nib
:Xcode
自带的数据文件,包含一个窗口程序和应用程序委托对象.sqlite
:数据库文件.txt
:文本文件.mom
:Xcode
创建的数据模型文件
这次测试之前IPA
未解压的大小是76.5MB
,解压之后的大小是140.1MB
解压后各个部分大小的明细如下
内容 | 大小 |
---|---|
_CodeSignature | 1.8MB |
Assets.car (部分使用) | 518KB |
embedded.mobileprovision | 55KB |
Info.plist | 16KB |
Plugins | 2.7MB |
Iproj | 2KB |
exec | 104.9MB |
图片资源(.png,.jpg,.webp,.gif ) | 16.7MB |
others | 13.4MB |
优化策略
上述了解完了IPA
各个组成之后,我们按照Xcode
编译优化,资源文件优化以及代码优化的顺序来一步一步进行分析
Xcode
编译优化
这个部分是最容易的,也是最容易忽略的,下面是笔者进行的总结,我们一个一个分析
编译指令集
首先查看我们目前的架构组成
augus@MacBookPro xxx.app % lipo -info xxx
Architectures in the fat file: sohunews are: armv7 arm64
我们再看下各个架构指令集对应的机型
# armv6: iPhone, iPhone 3G, iPod 1G/2G
# armv7: iPhone 3GS, iPhone 4, iPhone 4S, iPod 3G/4G/5G, iPad, iPad 2, iPad 3, iPad Mini
# armv7s: iPhone 5, iPhone 5c, iPad 4
# arm64: iPhone X,iPhone 8(Plus),iPhone 7(Plus),iPhone 6(Plus),iPhone 6s(Plus), iPhone 5s, iPad Air(2), Retina iPad Mini(2,3)
# arm64e: XS/XS Max/XR/ iPhone 11, iPhone 11 pro,iPhone 11 Pro Max,iPhone SE (2nd generation),iPhone 12 mini,iPhone 12,iPhone 12 Pro,iPhone 12 Pro Max,Phone 13 mini,Phone 13,iPhone 13 Pro,iPhone 13 Pro Max
# x86_64: 模拟器64位处理器
# i386: 模拟器32位处理器
此次测试中我们是不需要32位架构armv7
的,所以我们可以设置在打包的时候进行只打armv64
即可。
方法1:Build Settings
-Architectures
,把你需要的打包标识符下的设置为arm64
;Architectures
指定工程可以编译出多个指令集的代码包,ipa
就会变大;
方法2:Build Settings
-Excluded Arcitectures
,在你要的打包标识符下面增加两个配置项目Any iOS SDK
和Any iOS Simulator SDK
,然后分别设置为armv7
和arm64
;这个选项的意思是Release
模式下针对真机armv7
指令集排除,针对模拟器把arm64
排除。
这个优化之后,ipa
压缩包大小为51.4MB
,解压缩包大小变为90.9MB
,其中exec
变为56.6MB
,效果很明显
代码优化
Build Settings
-Optimization Level
在发布模式设置为[-Oz]
,其他模式根据场景进行选择,具体参考官方文档(https://help.apple.com/xcode/mac/11.4/#/itcaec37c2a6)进行设置;这个设置指的是生成的代码在速度和二进制大小方面的优化程度
Optimization Level
默认是-Os
,-Oz
是Xcode 11
新增的编译优化选项,该设置通过将重复的代码模式隔离到编译器生成的函数中来实现额外的尺寸节省
以下是不同的选项对应的编译速度和二进制文件大小变化趋势,来源于WWDC2019 Session 409 https://developer.apple.com/videos/play/wwdc2019/409/
优化完成之后ipa
大小是51MB
,解压缩包大小是87.9MB
资源目录优化
Build Settings
-Asset Catalog Compiler Options
-Optimization
设置为space
;这个选项可以改变actool
在构建Assets.car
时选取的编码压缩算法,减少包大小。
使用以下命令检查Assets.car
中图片的编码压缩算法
# 可以把对应信息生成.json文件,用于对比不同
xcrun --sdk iphoneos assetutil --info Assets.car > Assets.json
项目之前用的是默认配置,由于Assets.car
存放了少量资源,大部分都是存放在项目的根目录;
优化之后的ipa
包大小是51.3MB
,解压缩包大小是90.8MB
,Assets.car
大小是469KB
调试符号
Build Settings
-Generate Debug Symbols
设置为NO
;这个选项的意思是是否在源文件编程成.o
文件时,添加编译参数-g
和-gmodule
,就是generate complete debug info
,所以产生的.o
会变大,从而最终的可执行文件也就会变大。
需要注意的是,如果设置为NO
,在Xcode
中设置断点不会中断,不能进行断点调试。且最后不能生成dSYM
文件,即使Debug Infomation Format
设置了,也无法生成,因为生成的前提是得有调试信息,建议不要设置。
无用符号
Build Settings
-Deployment Postprocessing
,调试模式下NO
,发布模式下YES
Deployment Postprocessing
是Strip
的总开关。也就是说,只有Deployment Postprocessing
这里设置了YES
,Strip Debug Symbols During Copy
和Strip Linked Product
设置为YES
才会生效,其余情况均不生效
Strip Linked Product
:对最终的二进制文件是否进行去除无用符号Strip Debug Symbols During Copy
:文件拷贝编译阶段是否进行strip
,设置为YES
之后,会把拷贝进项目的第三方库、资源或者Extension
的调试符号剥离
这个优化之后,ipa
压缩包大小为51.4MB
,解压缩包大小变为90.9MB
,其中exec
变为56.6MB
,之前已经开启,所以无变化
第三个关于符号的优化是Build Settings
-Symbols Hidden by Default
,调试模式下为NO
,发布模式下为YES
。该选项的意思是当启用时,所有的符号多被声明为私有的extern
,除非在代码中使用attribute((visibility("default")))
明确标记为导出;反之,则所有符号都会被导出。
该测试项目中已开启,包大小无变化
复用字符串
Build Settings
-Make Strings Read-Only
设置为YES
;就是复用字符串字面。
该测试项目中已开启,包大小无变化
无效代码
Build Settings
-Dead Code Stripping
设置为YES
;是否消除无用代码
该测试项目中已开启,包大小无变化
异常捕获机制
Build Settings
-搜索exceptions
如果想要对项目瘦身,需要对途中的绿色未知设置为NO
Enable C++ Exceptions
和Enable Objective-C Exceptions
是指项目对错误的异常处理,比如try catch, throw
之类的语句;所以如果你的项目中有类似的处理,关闭这个之后会报错Cannot use '@try' with Objective-C exceptions disabled
。如果宏定义有try
,关闭之后也会报错
如果你的项目中可以设置关闭捕获异常的开关,还需要另外设置Build Settings
-Other Link Flags
去添加一个字段
-fno-exceptions
代表禁用异常机制,参考这里:
http://gcc.gnu.org/onlinedocs/gcc-4.7.0/libstdc++/manual/manual/using_exceptions.html
现在来总结下编译优化
编译选项 | _CodeSignature | Assets.car | embedded.mobileprovision | Info.plist | Plugins | Iproj | exec | 图片资源 | others | ipa | 解压缩包 |
---|---|---|---|---|---|---|---|---|---|---|---|
初始配置 | 1.8MB | 518KB | 55KB | 16KB | 2.7MB | 2KB | 104.9MB | 16.7MB | 13.4MB | 76.5MB | 140.1MB |
Architectures | 1.8MB | 518KB | 55KB | 17KB | 1.8MB | 2KB | 56.6MB | 16.7MB | 12.6MB | 51.4MB | 90.1MB |
Optimization | 1.8MB | 469KB | 55KB | 17KB | 1.8MB | 2KB | 56.6MB | 16.7MB | 13.4MB | 51.3MB | 90.8MB |
Optimization Level | 1.8MB | 469KB | 55KB | 17KB | 1.8MB | 2KB | 53.6MB | 16.7MB | 13.5MB | 51MB | 87.9MB |
资源文件优化
资源文件的优化就相对比较简单,但是比较繁琐;与上面的Xcode
编译配置不同,一次设置永久有效,资源的优化需要平时开发就需要关注,比如新资源的压缩,无用资源的删除等。
资源文件优化大体分为两个方向,第一个就是无用资源的删除,第二个就是已用资源的压缩。这里建议分先后顺序,就是先做删除,后做压缩,因为如果反过来,就会做一些无用功。
无用资源的删除
已定义未使用的代码文件 已废弃的业务,代码还在 已引用的图片但未使用 某些重复资源的导入
已用资源的优化
项目中引入的图片、网页、音频等文件的压缩
无用资源的删除
随着项目的迭代,每个项目都会或多或少存在冗余。可能是已经下线的业务,但是没人通知开发,于是代码逻辑一直存在;可能是删除某业务代码的时候,对应的图片资源为删除;又或者是多个开发导入了相同功能的第三方库。
对于这部分的优化分为预防和治理
关于预防
个人认为首先要有规范的流程,按流程执行,减少信息不对等的情况出现
产品和开发,广告和开发,都会导致相关业务的冗余,产品知道具体的业务数据,而开发不知道,所以通过某个固定时间的一次集中同步,让开发可以了解到自己的业务是否活跃,从而对项目进行相应的调整。
开发与开发之间的消息不对等,会导致各自开发自己的轮子,重复造轮子,所以可以通过建立公共文档、开发流程规范、项目使用第三方库规范、设计规范、代码规范都一一列举出来,每个人都能根据对应的文档了解到对应项目的信息,每个人开发都应该有一套统一的标准,这样就很大程度上避免了一人一套代码问题的出现。
具体规范流程可以根据自己公司或自己部分的实际情况来,多问为什么,比如这样
为什么会有类似的情况出现? 出现之后是否有处理? 怎么才能避免类似的事再次出现?
关于治理
已定义未使用的代码
如果你的项目代码在百万级以下,推荐你使用AppCode
来静态检查无用代码,如果超过了百万的团队,一般会自己通过Clang
静态分析来开发工具,去检查无用的代码和类
使用AppCode
做分析很简单,直接使用AppCode
打开你需要检查的工程,等待索引建立完成,点击顶部菜单的Code
-Inspect Code
然后选择扫描范围,默认是整个工程,可以自定义
然后等待静态分析完成即可,完成之后就会控制台显示Unused code
的提示
下面说下无用代码的类型
内容 | 中文翻译 |
---|---|
Unused class | 无用类 |
Unused global declaration | 无用的全局声明 |
Unused import statement | 无用的类引入声明 |
Unsed instance variable | 无用的实例变量 |
Unused local variable | 无用的局部变量 |
Unused macro | 无用的宏 |
Unused method | 无用的方法 |
Unused parameter | 无用的参数 |
Unused property | 无用的属性 |
Unused value | 无用的值 |
虽然AppCode
完成了大部分的工作,但是还是有一些问题存在,下面是我测试总结的问题
JSONModel
定义了未使用协议会被判定为无用协议如果子类使用了父类的方法,父类的这个方法会判断为未使用 通过点语法使用属性,该属性会被认为未使用 使用 performSelector
方式调用的方法检查不出来,比如[self performSelector:@selector(fetchPhotos:)]
,会认为fetchPhotos:
是未使用运行时声明的类也检查不出来,比如通过 NSClassFromString
方式调用的类会被查出未使用
id someClass = NSClassFromString(@"SomeClass");
// or
[[self class] getPhotos];
基于上述的原因,我们需要人工二次确认才能够安全删除代码,虽然繁琐,但是很安全。
已废弃业务,代码还在
定期对业务流程进行梳理,结合综合业务数据的埋点,同产品和广告确认功能是否已经下线,从而决定是否移除对应的业务模块
已引入未使用的图片
这里我测试使用的是LSUnusedResources
(https://github.com/tinymind/LSUnusedResources),针对使用编号规则的图片来说,可以直接添加规则来处理,使用也很简单;建议在删除前在项目中进行二次全局搜索确认,是否确实没有使用
某些重复资源的导入
重复资源分为静态库和项目文件
针对静态库,有多个相似功能进行需求整合,把最优解决方案的静态库留下,其余的跟相关产品和广告人员进行移除确认,比如线上崩溃采集听云、bugly
和matrix
等
针对项目文件,可以使用fdupes
工具进行重复文件扫描,该工具的原理是通过校验所有的资源的MD5
值,筛选出项目中重复的资源,文件比较顺序是
文件大小 > 部分 MD5
签名对比 > 完整MD5
签名对比 > 逐字节对比
fdupes
的安装和使用
# Searches the given path for duplicate files. Such files are found by
# comparing file sizes and MD5 signatures, followed by a byte-by-byte comparison.
# install fdupes
$ brew install fdupes
# where xxx is the directory to be scanned, and xxxFdupesResult.txt is the output file of the scan result
$ fdupes -Sr /User/augus/Documents/xxx > /User/augus/Documents/xxxFdupesResult.txt
已用资源的优化
已用资源的优化主要是就是图片、网页、json
、音频文件的压缩
网页的压缩,是放入App
资源中的js
或者html
文件,最好是经过 H5
端压缩后的
json
文件的压缩,如果不是即时使用的,可以放倒云端或者后台,进行网络获取后使用
音频的压缩,需要跟产品和广告进行沟通,在可接受范围内,选择系统可支持的压缩比最高的格式
最后说图片的压缩,分为以下几个部分
如果图片小于
100KB
则使用TinyPng
进行再一步压缩,使用TinyPng
压缩三次即可达到极限值;这里推荐一个TinyPng
的多图压缩的脚本(https://github.com/mokong/BatchProcessImage),因为在线最多一次最多20张,原理就是封装了官方的一个压缩接口,通过一个邮箱注册一个key,然后每个key
就是500次/月。将图片放入
xcassets
,因为xcassets
里的@2x
和@3x
图片,在上传时,会根据具体设备分开对应分辨率的图片,不会同时包含。而放入.bundle
中的都会包含,所以要尽量把图片放入xcassets
中。Assets.car
编译过程中有时会选择一些图片,拼凑成一张大图来提高图片的加载效率。被放进这张大图的小图会变为通过偏移量的引用,建议使用频率高且小的图片放到xcassets
中,xcassets
能保证加载和渲染速度最优。大于
100KB
就不要放入xcassets
中了。大的图片可以考虑将图片转成WebP。WebP是Google公司的一个开源项目,能够把图片压缩到很小,但是肉眼看不出来差别,目前iOS
常用的图片显示类库都支持该格式解析的拓展。可使用鹅厂的一个工具iSparta(http://isparta.github.io/)进行批量转换。
WebP在CPU消耗和解码时间上会比PNG高2倍,所以我们要在性能和体积上做取舍
监控机制
优化完成之后,如何保证包大小不会再次迅速增大?或者如何迅速定位增大原因,就需要依赖一套合适的监控机制和合理的流程规范来保证。
监控机制保证实时发现问题,每次打包完成后,运行脚本比较包大小差异,如果有增大超过了设置的阈值,则邮件通知相关负责人,负责人排查原因,同时做好记录;每次打包的包大小进行文档记录,造成包大小变化的原因记录
流程规范是用于保证每个项目开发者在开发中有良好的开发习惯,避免包大小突然增大,而一无所知
引入新的第三方库,要考虑项目中是否有相同类型的,是否可以自己实现,是否会造成体积增大 新增图片资源,关注大小,是否能用代码实现,进行适当压缩 及时清理废弃模块 每次打包关注包大小变化,文档记录,持续跟踪
结束语
包大小不是一时可以完成,需要平时每个相关人都去注意,有了良好的开发规范,开发流程就能持续进行改进,从而达到一个平衡稳定状态。
参考文档
Build Setting Reference https://help.apple.com/xcode/mac/11.4/#/itcaec37c2a6 包体积大小:瘦身 https://www.zybuluo.com/qidiandasheng/note/1662385) 抖音品质建设 - iOS 安装包大小优化实践篇 https://blog.csdn.net/ByteDanceTech/article/details/112504772
-End-
最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
面试题
】即可获取