目录
前言
随着版本迭代和业务的堆积,安装包会越来越大,安装包变大也带来了其他隐患,比如之前在App Store下载的应用超过150MB时,是无法使用流量下载的,只能连接到无线局域网下载。虽然现在可以设置允许所有App使用蜂窝数据下载,但是有个“超过200MB时请求许可”的选项可能是默认勾选的,而且对流量和手机存储空间敏感的用户,有时候安装包的大小就决定了是否拥有这个用户,所以,对于安装包的瘦身是App发展和优化过程中不可避免的重要一步。
一、安装包组成
iOS的安装包就是ipa,把一个ipa文件将后缀.ipa修改为.zip,然后将其解压出来,在Payload中的.app 选择显示包内容,就可以查看里面的资源文件。
.app 里面的主要内容
-
_CodeSignature:存放文件的 hash 列表。里面有一个文件 CodeResources ,这个文件是一个属性列表,包含 bundle 中所有其他文件的列表。这个属性列表只有一项 files,这是一个字典,键是文件名,值通常是 Base64 格式的散列值。如果键表示的文件是可选的,那么值本身也是一个字典,这个字典有一个 hash 键和一个 optional 键(布尔值 true)。它的作用是用来判断一个应用程序是否完好无损,能够防止不小心修改或损坏资源文件。
- 一些.bundle文件:bundle是一种标准化的层次结构,保存了可执行代码以及代码所需要的资源。bundle文件可以理解为一个资源包,用于存储图片、音频、文本、nib文件等,方便在其他项目中引用包内的资源。bundle包是静态的,不参与编译,也就意味着,bundle 包中不能包含可执行的文件。它仅仅是作为资源,被解析成为特定的二进制数据。从.app的包里可以看到,.bundle文件基本上都是一些SDK制作的。
- Assets.car:把放在Assets.xcassets中的图片(除了AppIcon和LaunchImage,这两种图片是直接放在包中的)打包后统一压缩成一个Assets.car的文件,减小包的大小。
- 一些.nib文件:使用xib创建的文件。
- .plist:一些属性列表文件。
- Frameworks:Framework 是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。
- 一些.png、.jpg、.mp3、.mp4等形式的图片和音视频资源。
二、资源瘦身优化
资源瘦身主要是去掉无用资源
和压缩资源,
资源包括图片
、音视频文件
、配置文件等,其中图片资源应该是用的最多的。
无用资源是指资源在工程文件里,但没有被代码引用。
2.1 无用资源删除
相关业务或模块移除后,对应的图片资源没有删除,造成安装包里面积累很多无用图片,清理的办法也比较简单,就是根据图片的名字在Xcode里全局搜索,如果没有搜到可能就是没有用到的图片,但是并不完全肯定,也有代码里有可能是通过字符串拼接的方式使用了图片。
查找并删除这些无用的图片资源流程是这样的
- 查找:使用find命令获取选定的目录下的所有指定类型的资源文件(比如png、jpg、gif等)。
- 匹配:使用正则匹配在代码中使用到的图片名,可以设置匹配规则,比如为了方便循环创建,这样命名的@"icon_%d"。
- 删除:所有的资源文件减去用到的资源文件就是无用的资源文件了,对这些无用的资源再确认一下删除就可以了。
推荐一个好用的开源工具 LSUnusedResources,使用起来很简单,搜索速度和匹配的准确性也很高。
除了图片资源外,音视频资源,像.json、plist、README.md 等这样的无用文件也可以删除掉。
2.2 资源压缩
对于有用的资源文件,可以通过无损压缩减少占用空间。尽量使用8-bit的PNG图片,比32-bit的图片能减少4倍的压缩率。由于8-bit的图片支持最多256种不同的颜色,所以8-bit的图片一般只应该用于一小部分的颜色图片。例如灰度图片最好使用8-bit。
2.3 图片管理方式
工程中的图片资源主要有两个方式管理,一种是在项目中添加文件夹存放,另一种是放在Assets.xcassets
管理。推荐使用Assets.xcassets
管理,因为它会把里边的所有 png
格式的图片打包成一个Assets.car文件,压缩比率比文件夹管理图片要高,而且可以根据不同的设备,不同的分辨率设置相应的图片。
2.4 动态下载资源
有些非必要的文件资源可以放在服务器,结合本地缓存策略,比如主题、皮肤、音乐这样的资源。
三、可执行文件优化
App 安装包主要是由资源文件和可执行文件(Mach-O)组成的,可执行文件大小是由代码量决定的。通常情况下,对可执行文件进行瘦身,就是找到并删除无用代码的过程。方法和图片资源清理类似:先找出方法和类的全集,再找到使用过的方法和类,取二者的差集就得到无用代码,人工二次确认后删除。
3.1 找到方法和类的全集:Link Map 文件分析
Xcode build产生的Link Map文件能比较直观的反映出程序各部分的文件大小情况,对于减少包体积很有帮助。
获取 LinkMap :将 Build Setting 里的 Write Link Map File 设置为 Yes,然后指定 Path to Link Map File 的路径就可以得到每次编译后的 LinkMap 文件了。我们只修改一下生成的 Link Map文件的路径就可以了,后缀名不要修改。
LinkMap 文件分为三部分:Object files、Section 和 Symbols。
- Object files:代码工程中所有文件编译后的目标文件.o;
- Section:描述了代码段在生成的 Mach-O 里的偏移位置和大小,包括代码段(__TEXT)和数据段(__DATA)的分布情况;
- Symbols:符号相关信息,会列出每个方法、类、block,以及它们的大小,第一列Address 是在文件中的偏移位置,第二列Size 是大小,第三列File 是对应上面Object files
中的文件
编号,第四列Name 是文件名。可以看到黄色框框部分跟上图Object files 是对应的。
通过对 LinkMap 的分析,我们不但可以统计出所有的方法和类,还能够清晰地看到代码所占包大小的具体分布,从而有针对性地对代码进行优化。对Symbols的分析还可以通过方法的二进制重排来提升冷启动速度(本篇不做过多介绍)。
3.2 找到已使用的方法和类: Mach-O 文件
Mach-O是Mach Object的缩写,是Mac/iOS上用于存储程序、库的标准格式。常见的Mach-O文件比如iOS开发好的代码打包好后就是Mach-O格式的文件。Xcode编译完工程会生成一个可执行程序,查找方式也简单,首先在Xcode的Preferences里找到这个路径:
在这个文件夹下找到对应的工程文件名/Build/Products/Debug,进入这个目录下,就可以找到我们的可执行文件了。
iOS 的方法都会通过 objc_msgSend 来调用。而objc_msgSend 在 Mach-O 文件里是通过 __objc_selrefs 这个 section 来获取 selector 这个参数的。所以,__objc_selrefs 里是被调用了的方法,__objc_classrefs 里是被调用过的类,__objc_superrefs 是调用过 super 的类。通过 __objc_classrefs 和 __objc_superrefs,我们就可以找出使用过的类和子类。可以通过MachOView来查看 Mach-O 文件里的信息。
对比Link Map文件就可以找出没有用到的类和方法了,但是Objective-C 是动态语言,方法可以在运行时动态调用,通过这种方法找到的无用代码还需人工确认才可以删除。
3.3 使用AppCode
如果不想这么麻烦地去分析对比,推荐一个好用的工具 AppCode,代码量不是很大时,AppCode通过静态分析可以快速帮我们找出没有用到的文件和方法,使用方式是在 AppCode 里选择 Code->Inspect Code 就可以进行静态分析。
分析结果也是清晰明了,包括:
Not implemented methods:没有实现的方法;
Key value coding:KVC相关,比如使用KVC访问了@private修饰的成员变量;
Unused class:没有用到的类;
Unused import statement:无用类引入声明;
Unused instance variable :无用的实例变量;
Unused method:没有用到的方法;
Unused property :没有用到的属性;
Unused parameter :无用参数;
Unused local variable :无用的局部变量;
Unused value :无用的值;
Unused macro :无用的宏;
Unused global declaration :无用全局声明。
但是检测结果并不是完全可靠,只是为我们优化代码提供参考方向,最好还是人工确认一下,不要完全相信工具。
四、编译选项优化
Xcode 支持编译器层面的一些优化优化选项,可以让我们在更快的编译速度、更小的二进制和更快的执行速度之间自由选择想要的优化程度。
- Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为YES,目前Xcode已经默认打开的,老项目注意检查;
- 去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO,Other C Flags添加-fno-exceptions;
- Strip Debug Symbols During Copy设置为YES,这个是将那些拷贝进项目包的三方库、资源或者 Extension 的 Debug Symbol 去除掉。这个选项没有前置条件,只需在Release模式下开启,否则就不能对三方库进行断点调试和符号化了;
- Build Settings -> Optimization Level有几个编译优化选项,release版应该选择Fastest, Smalllest,这个选项会开启那些不增加代码大小的全部优化,并让可执行文件尽可能小;
- Enable BitCode设置为YES;
总结
业务的不断堆积迭代,总会产生一些无用的资源,所以安装包瘦身要定期清理这些无用文件和代码。除了自己写的代码外对用到的三方库也可以做检查优化,比如有的库还支持Mac的i386和x86_64架构处理器,这部分代码就可以根据实际情况删除;对比SDK和三方库的导入对ipa大小的影响,使用满足需求的较小库;项目中重复方法抽离出来等,这些优化可能对安装包的大小影响很小,但是积少成多,形成对ipa大小敏感的习惯也能帮助我们写出效率更高的代码。对安装包瘦身的探索还有很长的路走,本文也只是列举了一些常用的瘦身方案,对于庞大的项目,操作起来也是如履薄冰,还好在没有影响业务和正常功能使用的情况下,我们的ipa减少了大约15MB,优化之路还在继续,以后还会补充更多的方案。
来源:oschina
链接:https://my.oschina.net/u/4342648/blog/4565790