随着项目业务的增加,我们的APP体积越来越大,直接影响到用户的安装体验。目前,苹果对大于200M的包,无法使用蜂窝数据下载,因此,秉着优化项目,减小体积的目的,对APP针对性的进行瘦包。
安装包构成
想要进行APP的包大小优化,首先就要了解包构成,如下图:
我们需要优化的东西,都在Payload当中,大致可将文件分类为:
- 可执行文件 Mach-O
- 资源文件
- 图片
- xib、storyboard
- 其他资源:音视频、网页、文本
- framework
安装包大小
我们在Archive后,生成 .ipa 的包,解压打开后,Payload下就是 .app 文件。
- .ipa 是应用程序归档文件,是提交到 APP Store Connect的文件
- .app 是最终安装到硬件设备上的文件。
包的大小如何确定,并不是Archive完成后的大小,而是上传到APP Store Connect上,构建完成版本的大小,如下图:
可以看到,构建版本分两种,一种是Universal另外一类是各种设备。Universal 指通用设备,未经Slicing(下文会说)优化的包,因此体积很大。
构建版本的下载大小比Archive出来的ipa要大一点,因为App Store Connect做加密处理。一定要注意,目前App Store对大于200M的下载大小,限制其蜂窝网络下载。
App Thinning
App Thinning是iOS9之后引入的优化措施,是用来降低应用分发到不同设备时候的下载大小。包括三项主要的功能:
- Slicing
- Bitcode
- On-Demand Resources
Slicing
什么是Slicing,打包后,传到App Store Connect后,在构建过程中,App会被分割成各种变体(variant),以此来适配不同的硬件设备,从而减小安装包,节省资源。
变体的分割,如下图,会根据设备属性,来分配资源,尤其是图片资源,比如@2x
和@3x
的图片,放入Asset Catalog
中,就会被细分。在Boundle
中则不会细分。
所以,能放入Asset Catalog
中,就放进去吧,资源不规范,苹果两行泪啊。
Bitcode
对于Bitcode,第一次遇见(Xcode7开始),那就是这玩意打开要报错。
Bitcode实际上是一种程序中间码,直接优化的是我们的可执行程序,这些优化都是在App Store服务端完成。
目的是为了适应未来新架构或新的编译优化,App store直接生成对应的变体,而不需要我们重新发版。
对于iOS,Bitcode可选,对tvOS、watchOS,Bitcode必须开启。
对于开启Bitcode,需要注意的是,工程必须全部开启,dSYM需要重新下载
- 工程全部开启,就是需要项目中的所有动态库、静态库都包含Bitcode,Pod第三方库也要开启,否则编译失败。
- 符号表重新下载,打包上传完后,在构建版本那里,重新下载符号表。
On Demand Resources
ODR(on-demand resources 随需应变资源),是iOS资源管理的另一种方法,多用在分级游戏当中。
ODR可以使用户只下载他当前等级适用的资源,从而减小资源占用。在Xcode9以后,该功能默认开启。
一般的ODR流程如下图。用户点击内容的时候,会下载对应的资源。
ODR完整生命周期如下图。
ODR设置
Xcode9以后,默认开启ODR,在开发过程中,可以通过设置tag来识别资源。
1.开启ODR
2.创建Tags
3.创建文件Tag
4.创建图片Tag
5.Tag优先级
三种优先级:
- Initial install tags,这类资源与App同时下载。当没有NSBundleResourceRequest对象访问它们时,它们将会从设备上清除。
- Prefetch tag order,在App安装后开始下载,按照预加载列表中的顺序依次下载。
- Dowloaded only on demand,只有在App中发出请求时才会下载。
有关ODR资源的大小限制
优化方案
了解了以上知识之后,我们开始着手做优化。
从一开始的安装包结构,和文件分类来看,从可执行程序,以及资源文件入手。
Architectures
Architectures的设置,直接影响到最终生成的可执行程序的大小。
都0202年了,放弃32位吧,从iOS9开始,全系64位。
这里简单说明一下iOS处理器架构和指令集的问题。苹果的指令集有armv6
、armv7
、armv7s
、arm64
以及最新的arm64e
。
指令集向下兼容,但是指令集与设备不匹配,会影响执行效率,这个我们管不到。
指令集 | 支持设备 |
---|---|
armv6 | iPhone, iPhone2, iPhone3G, 第一代、第二代 iPod Touch |
armv7 | iPhone3GS,iPhone4,iPhone4S,iPad,iPad2,iPad3(The New iPad),iPad mini,iPod Touch 3G,iPod Touch4 |
armv7s | iPhone5,iPhone5C,iPad4(iPad with Retina Display) |
arm64 | iPhone5S以上,iPad Air,iPad mini2(iPad mini with Retina Display) |
arm64e | iPhone XS,iPhone XS Max,iPhone XR 使用A12或以上的设备 |
可以看到,目前来说,可以只用armv7s
、arm64
、arm64e
如何设置
Architectures
默认为标准设置,用来指定工程编译后可支持的指令集,也就是支持哪些设备,支持的越多,打出来的包越大。
Valid Architectures
该编译项指定可能支持的指令集,限制指令集范围,该列表和Architectures列表的交集,将是Xcode最终生成二进制包所支持的指令集。
所以,Architectures设置为标准,Valid Architectures设置为
armv7s
、arm64
、arm64e
。
编译选项优化
编译的设置,对Debug
和Release
时的可执行文件有直接影响
Asset Catalog Compiler
optimization 选项设置为 space 可以减少包大小
Dead Code Stripping
删除静态链接的可执行文件中未引用的代码
Debug 设置为 NO, Release 设置为 YES 可减少可执行文件大小。
Xcode 默认会开启此选项,C/C++/Swift 等静态语言编译器会在 link 的时候移除未使用的代码,但是对于 Objective-C 等动态语言是无效的。因为 Objective-C 是建立在运行时上面的,底层暴露给编译器的都是 Runtime 源码编译结果,所有的部分应该都是会被判别为有效代码。
Apple Clang - Code Generation
Optimization Level 编译参数决定了程序在编译过程中的两个指标:编译速度和内存的占用,也决定了编译之后可执行结果的两个指标:速度和文件大小。
默认情况下,Debug 设定为 None[-O0] ,Release 设定为 Fastest,Smallest[-Os]。
6个级别对应的含义如下:
None[-O0]。 Debug 默认级别。不进行任何优化,直接将源代码编译到执行文件中,结果不进行任何重排,编译时比较长。主要用于调试程序,可以进行设置断点、改变变量 、计算表达式等调试工作。
Fast[-O,O1]。最常用的优化级别,不考虑速度和文件大小权衡问题。与-O0级别相比,它生成的文件更小,可执行的速度更快,编译时间更少。
Faster[-O2]。在-O1级别基础上再进行优化,增加指令调度的优化。与-O1级别相,它生成的文件大小没有变大,编译时间变长了,编译期间占用的内存更多了,但程序的运行速度有所提高。
Fastest[-O3]。在-O2和-O1级别上进行优化,该级别可能会提高程序的运行速度,但是也会增加文件的大小。
Fastest Smallest[-Os]。Release 默认级别。这种级别用于在有限的内存和磁盘空间下生成尽可能小的文件。由于使用了很好的缓存技术,它在某些情况下也会有很快的运行速度。
Fastest, Aggressive Optimization[-Ofast]。 它是一种更为激进的编译参数, 它以点浮点数的精度为代价。
Link-Time Optimization
Link-Time Optimization 是 LLVM 编译器的一个特性,用于在 link 中间代码时,对全局代码进行优化。这个优化是自动完成的,因此不需要修改现有的代码;这个优化也是高效的,因为可以在全局视角下优化代码。
的优化主要体现在如下几个方面:
多余代码去除(Dead code elimination)如果一段代码分布在多个文件中,但是从来没有被使用,普通的 -O3 优化方法不能发现跨中间代码文件的多余代码,因此是一个“局部优化”。但是Link-Time Optimization 技术可以在 link 时发现跨中间代码文件的多余代码。
跨过程优化(Interprocedural analysis and optimization)这是一个相对广泛的概念。举个例子来说,如果一个 if 方法的某个分支永不可能执行,那么在最后生成的二进制文件中就不应该有这个分支的代码。
内联优化(Inlining optimization)内联优化形象来说,就是在汇编中不使用 “call func_name” 语句,直接将外部方法内的语句“复制”到调用者的代码段内。这样做的好处是不用进行调用函数前的压栈、调用函数后的出栈操作,提高运行效率与栈空间利用率。
开启这个优化后,一方面减少了汇编代码的体积,一方面提高了代码的运行效率。
Resources
对于资源方面,我们可以采用以下方法处理:
- 删除无用文件
- 删除重复文件
- 压缩大文件
- 更合理的图片管理方式
删除无用文件 删除重复文件
工程里面的无用文件,尤其是代码文件,音视频等,需要人工排查。
对于无用的,多余的图片,需要借助工具来进行排查。
主要使用LSUnusedResources来进行重复文件排查。
该工具并不能精确的找出重复文件和未使用的文件,还是需要人工来对结果进行排查。具体的使用,可以参考这篇文章
压缩大文件
使用imageoptim来压缩图片是个不错的选择。
imageOptim是一款基于Mac的图像“瘦身”软件,内置有6种压缩算法,通过删除图片部分无用的EXIF等信息来减小PNG、JPEG和GIF图片的大小。
ImageOptim合并了OptiPNG、PNGCrush、AdvanceComp、PNGOUT、Jpegoptim+Jpegtran和Gifsicle等几个工具,旨在为设计师提供最好的优化效果。
在最新发布的1.4.4版本中,ImageOptim改进了文件在文件列表中的拖拽、复制、粘贴功能。
Xcode也为我们提供了图片压缩的选项。
Compress PNG Files 打包的时候自动对图片进行无损压缩,使用的工具为 pngcrush,压缩比还是相当高的,比较流行的压缩软件 ImageOptim 也是使用 pngcrush 进行压缩 PNG 的。
Remove Text Medadata From PNG Files 移除 PNG 资源的文本字符,比如图像名称、作者、版权、创作时间、注释等信息。
所以,对于图片压缩,我们建议在Xcode开启压缩后,再使用ImageOptim处理一次。
更合理的图片管理方式
ipa包过大,大部分原因是没有对图片进行规范化的管理。
首先来看看图片文件的导入方式:
导入方式 | 优缺点 |
---|---|
加入到Assets.xcassets中 | 1.只支持png格式的图片,jpeg会变大 2.图片只支持 [UIImage imageNamed] 的方式实例化,但是不能从Bundle中加载3.在编译时, Images.xcassets 中的所有文件会被打包为Assets.car 的文件 |
CreateGroup | 1.黄色文件夹图标;Xcode中分文件夹,Bundle 中所有所在都在同一个文件夹下,因此,不能出现文件重名的情况2.可以直接使用 [NSBundle mainBundle] 作为资源路径,效率高!3.可以使用 [UIImage imageNamed:] 加载图像 |
CreateFolderRefences | 1.蓝色文件夹;Xcode中分文件夹,Bundle中同样分文件夹,因此,可以出现文件重名的情况 2.需要在 [NSBundle mainBundle] 的基础上拼接实际的路径,效率较差3.不能使用 [UIImage imageNamed:] 加载图 |
PDFs矢量图(Xcode6+) | 1.使用该方式,只需要一张图,就能自动生成2x3x图 2.上传App store Connect之后,会发生体积膨胀的问题。 |
Bundle(包)中的图片素材 | 1.支持多语言 2.可使用 imageWithContentsOfFile 的方式加入3.包含所有设备图片,不会进行Slicing |
小Tips:使用
imageNamed
创建的UIImage
,会立即被加入到NSCache
中(解码后的 Image Buffer),直到收到内存警告的时候,才会释放不在使用的UIImage
。而imageWithContentsOfFile
。它每次都会重新申请内存,相同图片不会缓存。所以,xcassets 内的图片,加载后会产生缓存
使用CreateGroup、CreateFolderRefences两种方式打出来的包,图片都会直接放在.app文件中,所以打包前后,图片的大小不会改变。
加入到Assets.xcassets中的方法则不同,打包后,在.app
中会生成Assets.car文件来存储Assets.xcassets中的图片,并且文件大小方面也大大降低
对于打包后的Assets.car中的文件如何查看,可以使用工具Asset Catalog Tinkerer打开
对于放入Assets.xcassets的图片要注意一下几点
- 不要放入过大的图片,会占用内存。理论上,最好不要大于100kb
- 不要放入jpeg格式的图片,体积会膨胀
所以,建议对图片管理,较小的图片尽量放入Assets.xcassets中进行管理,大图,放入Boundle中管理
总结
APP瘦包的方法还有很多,以上介绍的也只是一些通用和常用的方法。
使用哪种方法,还要根据项目的具体情况而定。
瘦包CheckList
- 1.删除重复文件,无用文件。
- 2.压缩大文件。使用imageOptim工具。
- 3.编译优化。
参考文章