EdwardJ

Stay Hungry Stay Foolish

0%

iOS安装包大小优化

随着项目业务的增加,我们的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 ThinningiOS9之后引入的优化措施,是用来降低应用分发到不同设备时候的下载大小。包括三项主要的功能:

  • Slicing
  • Bitcode
  • On-Demand Resources

Slicing

什么是Slicing,打包后,传到App Store Connect后,在构建过程中,App会被分割成各种变体(variant),以此来适配不同的硬件设备,从而减小安装包,节省资源。

Slicing

变体的分割,如下图,会根据设备属性,来分配资源,尤其是图片资源,比如@2x@3x的图片,放入Asset Catalog中,就会被细分。在Boundle中则不会细分。
所以,能放入Asset Catalog中,就放进去吧,资源不规范,苹果两行泪啊。

variant

Bitcode

对于Bitcode,第一次遇见(Xcode7开始),那就是这玩意打开要报错。
Bitcode实际上是一种程序中间码,直接优化的是我们的可执行程序,这些优化都是在App Store服务端完成。
目的是为了适应未来新架构或新的编译优化,App store直接生成对应的变体,而不需要我们重新发版。
对于iOS,Bitcode可选,对tvOS、watchOS,Bitcode必须开启。

BitCode Set

对于开启Bitcode,需要注意的是,工程必须全部开启dSYM需要重新下载

  • 工程全部开启,就是需要项目中的所有动态库、静态库都包含Bitcode,Pod第三方库也要开启,否则编译失败。
  • 符号表重新下载,打包上传完后,在构建版本那里,重新下载符号表。

On Demand Resources

ODR(on-demand resources 随需应变资源),是iOS资源管理的另一种方法,多用在分级游戏当中。
ODR可以使用户只下载他当前等级适用的资源,从而减小资源占用。在Xcode9以后,该功能默认开启。

On Demand Resources

一般的ODR流程如下图。用户点击内容的时候,会下载对应的资源。
On Demand Resources

ODR完整生命周期如下图。
On Demand Resources

ODR设置

Xcode9以后,默认开启ODR,在开发过程中,可以通过设置tag来识别资源。

1.开启ODR
ODRSet

2.创建Tags
ODR创建Tag
ODR创建Tag

3.创建文件Tag
ODR文件Tag

4.创建图片Tag
ODR图片Tag

5.Tag优先级

ODRTag优先级

三种优先级:

  • Initial install tags,这类资源与App同时下载。当没有NSBundleResourceRequest对象访问它们时,它们将会从设备上清除。
  • Prefetch tag order,在App安装后开始下载,按照预加载列表中的顺序依次下载。
  • Dowloaded only on demand,只有在App中发出请求时才会下载。

有关ODR资源的大小限制
ODR资源大小限制

优化方案

了解了以上知识之后,我们开始着手做优化。
从一开始的安装包结构,和文件分类来看,从可执行程序,以及资源文件入手。

Architectures

Architectures的设置,直接影响到最终生成的可执行程序的大小。
都0202年了,放弃32位吧,从iOS9开始,全系64位。

这里简单说明一下iOS处理器架构和指令集的问题。苹果的指令集有armv6armv7armv7sarm64以及最新的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或以上的设备

可以看到,目前来说,可以只用armv7sarm64arm64e

如何设置

Architectures

Architectures
默认为标准设置,用来指定工程编译后可支持的指令集,也就是支持哪些设备,支持的越多,打出来的包越大。

Valid Architectures
该编译项指定可能支持的指令集,限制指令集范围,该列表和Architectures列表的交集,将是Xcode最终生成二进制包所支持的指令集。

所以,Architectures设置为标准,Valid Architectures设置为armv7sarm64arm64e

编译选项优化

编译的设置,对DebugRelease时的可执行文件有直接影响

Asset Catalog Compiler

optimization 选项设置为 space 可以减少包大小
AssetCatalogCompiler

Dead Code Stripping

删除静态链接的可执行文件中未引用的代码

Debug 设置为 NO, Release 设置为 YES 可减少可执行文件大小。

Xcode 默认会开启此选项,C/C++/Swift 等静态语言编译器会在 link 的时候移除未使用的代码,但是对于 Objective-C 等动态语言是无效的。因为 Objective-C 是建立在运行时上面的,底层暴露给编译器的都是 Runtime 源码编译结果,所有的部分应该都是会被判别为有效代码。

DeadCodeStripping

Apple Clang - Code Generation

Optimization Level 编译参数决定了程序在编译过程中的两个指标:编译速度和内存的占用,也决定了编译之后可执行结果的两个指标:速度和文件大小。

AppleClang

默认情况下,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 是 LLVM 编译器的一个特性,用于在 link 中间代码时,对全局代码进行优化。这个优化是自动完成的,因此不需要修改现有的代码;这个优化也是高效的,因为可以在全局视角下优化代码。

的优化主要体现在如下几个方面:

  • 多余代码去除(Dead code elimination)如果一段代码分布在多个文件中,但是从来没有被使用,普通的 -O3 优化方法不能发现跨中间代码文件的多余代码,因此是一个“局部优化”。但是Link-Time Optimization 技术可以在 link 时发现跨中间代码文件的多余代码。

  • 跨过程优化(Interprocedural analysis and optimization)这是一个相对广泛的概念。举个例子来说,如果一个 if 方法的某个分支永不可能执行,那么在最后生成的二进制文件中就不应该有这个分支的代码。

  • 内联优化(Inlining optimization)内联优化形象来说,就是在汇编中不使用 “call func_name” 语句,直接将外部方法内的语句“复制”到调用者的代码段内。这样做的好处是不用进行调用函数前的压栈、调用函数后的出栈操作,提高运行效率与栈空间利用率。

LTO

开启这个优化后,一方面减少了汇编代码的体积,一方面提高了代码的运行效率。

Resources

对于资源方面,我们可以采用以下方法处理:

  • 删除无用文件
  • 删除重复文件
  • 压缩大文件
  • 更合理的图片管理方式

删除无用文件 删除重复文件

工程里面的无用文件,尤其是代码文件,音视频等,需要人工排查。
对于无用的,多余的图片,需要借助工具来进行排查。

主要使用LSUnusedResources来进行重复文件排查。
LSUnusedResources

该工具并不能精确的找出重复文件和未使用的文件,还是需要人工来对结果进行排查。具体的使用,可以参考这篇文章

压缩大文件

使用imageoptim来压缩图片是个不错的选择。

imageoptim

imageOptim是一款基于Mac的图像“瘦身”软件,内置有6种压缩算法,通过删除图片部分无用的EXIF等信息来减小PNG、JPEG和GIF图片的大小。
ImageOptim合并了OptiPNG、PNGCrush、AdvanceComp、PNGOUT、Jpegoptim+Jpegtran和Gifsicle等几个工具,旨在为设计师提供最好的优化效果。
在最新发布的1.4.4版本中,ImageOptim改进了文件在文件列表中的拖拽、复制、粘贴功能。

Xcode也为我们提供了图片压缩的选项。
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打开
Asset Catalog Tinkerer

对于放入Assets.xcassets的图片要注意一下几点

  • 不要放入过大的图片,会占用内存。理论上,最好不要大于100kb
  • 不要放入jpeg格式的图片,体积会膨胀

所以,建议对图片管理,较小的图片尽量放入Assets.xcassets中进行管理,大图,放入Boundle中管理

总结

APP瘦包的方法还有很多,以上介绍的也只是一些通用和常用的方法。
使用哪种方法,还要根据项目的具体情况而定。

瘦包CheckList

  • 1.删除重复文件,无用文件。
  • 2.压缩大文件。使用imageOptim工具。
  • 3.编译优化。

参考文章

  1. iOS 瘦包,常见方式梳理
  2. 优化安装包大小
  3. ipa文件“减肥”初探
如果内容对你有用,赏我一杯咖啡未尝不可^_^