背景知识
App的大小分为下载大小和安装大小。
下载大小指的是App压缩包(.ipa)所占的空间大小。用户下载的是压缩包,下载完成后会自动解压,安装大小就是指压缩包解压后的大小。
下载大小如果超过200MB,iOS13以下无法通过蜂窝网络下载;iOS13以上会通过用户手动设置才可以下载。
包大小优化
网上有很多包大小优化的方案,我看了很多博客,根据博客总结一下包大小优化相关的操作,如下图。
Xcode编译设置
- 去掉异常支持,Build Setting中Enable C++ Exceptions和Enable Objective-C Exceptions设置为NO,Other C Flags添加-fno-exceptions
注意⚠️:Enable C++ Excptions和Enable Objective-C Exceptions是指项目支持异常处理,关闭后try cache、throw、包括宏定义try{}、@finally{}、@strongify会报错。
-fno-exceptions的意思是禁用异常机制,参考gcc,同样,当项目中有try thorw的时候,就不要设置这个选项为NO。
Before detailing the library support for -fno-exceptions, first a passing note on the things lost when this flag is used: it will break exceptions trying to pass through code compiled with -fno-exceptions whether or not that code has any try or catch constructs. If you might have some code that throws, you shouldn't use -fno-exceptions. If you have some code that uses try or catch, you shouldn't use -fno-exceptions.
2.Build Settings -> Architectures,在Excluded Architectures中设置Release模式下 Any iOS SDK -> armv7,设置之后在Release下把armv7排除。
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 x86_64: 模拟器64位处理器 i386: 模拟器32位处理器
这里:iOS9之后推出了App Thinning,在这过程中苹果已经帮忙去掉armv7了。
3.Build Settings -> Generate Debug Symbols设置为NO
Generate Debug Symbols是生成调试符号,断点不能用,最后不能生成DSYM文件(DSYM文件是指具有调试信息的目标文件)。建议不要设置为NO。
- Build Settings -> Deployment Postprocessing,Debug模式下设置NO,Release下设为YES
Deployment Postprocessing是Strip配置的总开关
Build Settings -> Strip Linked Product,Debug下设置为NO,Release下设置为YES。
设置为Yes时,对最后的二进制文件进行strip,去除不必要的符号信息。
注意⚠️:去除了符号信息之后需要使用dSYM来进行符号化,所以需要将 Debug Information Format 修改为DWARF with dSYM file(Release下),如果在Debug下设置为DWARF with dSYM file那么在崩溃时将无法看到堆栈信息。
Build Settings -> Strip Debug Symbols During Copy,Debug下设置为NO,Release下设置为YES
文件拷贝编译阶段是否进行strip,设置为YES之后,会把拷贝进项目包的三方库、资源或者Extension的Debug Symbol去除。
5.Build Settings -> Symbols Hidden by Default,Debug模式下设置为NO,Release下设置为YES
Symbols Hidden by Default会把所有符号都定义成"private extern",移除符号信息
6.Build Settings -> Make Strings Read-Only设置为YES
复用字符串字面量
7.Build Settings -> Dead Code Stripping设置为YES
消除无效代码,C/C++/Swift 等静态语言编译器会在 link 的时候移除未使用的代码,对于OC等动态语言是无效的
- Asset Catalog Compiler编译设置优化,Build Settings ->
Compiler - Options 中Optimization改为space
这个选项可以改变actool在构建Assets.car时选取的编码压缩算法,减少包大小
9.Build Settins -> Optimization Level改为-Oz
Optimization Level默认为-Os,-Oz是Xcode 11之后才出现的编译优化选项,核心原理是对重复的连续机器指令外联成函数进行复用,因此开启Oz,能减少二进制的大小,但同时会带来执行效率但额外消耗。
Optimization Level各参数优化的选择对比,如下图,对于性能要求高的,建议选择-O2和-O3,对于包大小敏感的,可选择-Os和-Oz,默认-Os是性能和大小平衡比较好的。最终选择什么,需要读者根据自己实际项目而定。
Pod设置
在OC的项目中,Podfile如果引用了Swift的第三方库,一般都会直接打开use_frameworks!,对应的Pod中所有的库都会
打包成动态库,以及Swift和OC库的依赖问题会导致依赖库增加,会造成包体积增大。所以我们可以有优化为针对单个Swift库使用use_frameworks!
我按照上述修改后,pod install报错了,修改方法如下图。
资源优化
删除未使用的类
查看了一些文档后,我使用了python脚本的方法来查找项目中未使用的类。脚本地址如下:
脚本地址
执行方法:
1.cd到脚本所在文件夹中
2.运行下需要检测的项目后按照如下步骤找到.app文件所在文件夹
3.执行python脚本
python FindClassUnRefs.py -p /Users/a58/Library/Developer/Xcode/DerivedData/XXX-bqqoxganvkvgwuefbskxsbvnxlnn/Build/Products/Debug-iphonesimulator/XXX.app -w JD,BD,AL
参数说明:
-p Xcode运行之后的,项目Product路径
-w 结果白名单处理,检测结果,只想要以什么开头的类,多个用逗号隔开,比如JD,BD,AL
-b 结果黑名单处理,检测结果,不想要以什么开头的类,多个用逗号隔开,比如Pod,AF,SD
-w 和 -b 不能共存,共存会报错
用这个方法并未查找到未使用的category,并且还是需要在项目中进行搜索判断是否使用该类,结果并不完全准确。
删除未使用图片
通过工具LSUnusedResources,运行后,输入项目文件位置,即可查找未使用的图片。
LSUnusedResources下载地址
原理大致是遍历资源目录下后缀 ["imageset", "jpg", "png"...] 的文件,然后在源文件 ["m", "swift", "xib", "storyboard"...] 中字符串匹配,无匹配则是无用的资源文件。
使用时注意勾选Ignore similar name,然后点击右上角的Browse选中要扫描的项目地址,点击右下角的search,就会开始扫描,结果会在底部Unused Results中展示出来,然后CMD+A全选,export,导出到一个文本文件中。也可以在对应单条Item上面双击,会打开对应的文件夹。建议删除前在项目中搜索确认,是否确实没有使用(类似字符串中间替换的可能会被扫描出来,所以删除前需要确认)
压缩图片
可以通过以下网站压缩图片
tinyPNG
总结:
以上就是我包大小优化的过程,查看了一些博客,然后根据博客上所说的进行修改。以下是我打包两个.ipa包的大小对照图,从32M优化到了19.8M。
Mach-O文件
我们讲.ipa文件后缀修改为.zip后解压,会看到文件中所包含的内容。主要有codeSignature、Assets.car、还有一个exe文件,这个文件的格式是Mach-O。
在这里也可以看到nib文件,因为项目中使用xib文件绘制UI。由此可以知道,如果想要控制包大小,最好不要使用xib文件(个人理解)。
可以使用MachView来查看Mach-O文件,MachView下载地址如下
MachView下载地址
Mach-O文件主要包括三部分:
- Header:文件基本属性、CPU的类型、Load Commands的个数等。
- Load Commands:由多条Load Command组成,它们描述了Data在二进制文件和虚拟内存中布局,有了这个布局就能够知道Data的排布情况。
- Data:存储的实际内容,主要是程序的指令和数据,按照Load Commands描述排布。
使用以下指令可以查看Mach-O文件Data部分的结构和大小信息。
xcrun size -lm /Users/boyankeji/Downloads/LianCheng\ 2022-08-09\ 16-43-21/Payload/连成物联.app/连成物联
如图所示,主要包括了4部分:
- __PAGEZERO
- __TEXT
- __DATA
- __LINKEDIT
__PAGEZERO在内存中不可读不可写,用于捕获NULL指针。它并不占用Data的空间。
__TEXT、__DATA保存代码的指令和数据。
__LINKEDIT包含启动App需要的信息。
在今日头条优化实践: iOS 包大小二进制优化,一行代码减少 60 MB 下载大小这个博客中的包大小优化方法,主要就是将__TEXT段进行迁移。
其中原理是:
When your app is approved for the App Store, it is encrypted with DRM and recompressed. The added encryption and DRM affects the ability to compress your binary, and as a result you may see a larger App Store file size for your binary than the binary you uploaded on App Store Connect. The exact final size for your app cannot be determined in advance to the accuracy of a single byte.
我们将项目进行Archive后生成.xcarchive文件,文件上传到App Store Connect后,苹果会对可执行文件进行加密,然后再将App压缩成.ipa文件,然后发布到App Store。
加密会影响可执行文件的压缩效率,从而导致ipa大小增加,而这种加密效果几乎没用。
Mach-O 文件代码的解密发生在 Mach-O 文件被加载的时候,由 Mach Loader 进行。Mach Loader 会读取 Mach-O 中的 LC_ENCRYPTION_INFO 这条 Load Command 来判断可执行文件是否加密。
cryptid 表示加密方法为 1,如果为 0 表示不加密。
通过以下指令可以查看Load Commands
otool -l /Users/boyankeji/Downloads/LianCheng\ 2022-08-09\ 16-43-21/Payload/连成物联.app/连成物联
我们通过查看__TEXT的filesize,通过计算可以知道加密的内容主要在__TEXT上。所以我们将__TEXT移走可以达到减少包大小的目的。
注意⚠️:
苹果在 iOS 13 已经对下载大小做了优化,所以本方案无法再对 iOS 13 的设备的下载大小进一步优化。
即,若用户的设备 < iOS 13,那么本方案可以减少该设备上 App 32~34%的下载大小;
若用户的设备 >= iOS 13,本方案不会对该设备的 App 的下载大小有进一步优化,也不会有负面影响。
包大小优化总结
根据查找的博客来看,包大小优化主要是三部分:
- Xcode中删除编译需要的Symbols、Debug符号等;
- 对资源的控制,不要重复引入图片、过大的资源文件需要进行压缩;未用的代码、类需要及时删除;
- 黑科技,根据头条给出的方案,迁移__TEXT
以上是我根据最近一段时间的学习总结的,在学习的工程中,会发现自己对程序的构建、脚本的使用、Mach-O文件等都不太了解,现在通过学习感觉也只是皮毛的阶段。以后如果有时间需要更加深度的学习。
通过学习包大小优化的内容,以后在开发的过程中需要增加包大小优化的意识。
学习链接
App Store上的包大小
分析iOS包大小优化
iOS 脚本查看项目中未使用的类
iOS检测项目中无用类和方法
今日头条优化实践: iOS 包大小二进制优化,一行代码减少 60 MB 下载大小
iOS底层探索- Mach-O文件