闲散心如月,风光好自知

一、安装包组成分析

1、组成情况

将IPA包修改后缀名为ZIP,解压缩后,获取payload中的App文件,查看App文件的内容,你会发现该文件主要包含以下内容

  • Exectutable: 可执行文件
  • Resources:资源文件
  • 图片资源:Assets.car/bundle/png/jpg 等
  • 视频/音频资源:mp4/mp3 等
  • 静态网页资源:html/css/js 等
  • 视图资源:xib/storyboard 等
  • 其他:文本/字体/证书 等
  • Framework:
  • SwiftSupport: libSwiftxxx 等一系列 Swift 库
  • 其他依赖库:Embeded Framework
  • Pulgins:Application Extensions
  • appex:其组成大致与 ipa 包组成一致
2、组成分析
  • 一般来说,可执行文件、图片资源(asset.car)和动态库的占比最大,如果是Swift和OC混编,可执行文件比纯OC大很多
  • 从优化的效果上看,优化图片资源的ROI比较大,如果是首次优化,建议从图片资源的优化开始。
  • 项目中使用Swift,会增加安装包大小,因为FrameWork中会加入为了支持 Swift 的动态库集合,如果纯Swift项目,不会引入这些东西。

二、资源文件优化

理论上,资源文件包括:图片**、视频、**音频和字体等;实际上,视频和音频文件一般不会集成到安装包中,在安装包中的资源文件主要是图片。

1、优化手段1:App Slicing
  • iOS 9之后提供了App Thinning三件套:App Slicing、On Demand Resouces、Bitcode;

App Thinning

理想

现实

App Slicing

将App Bundle资源根据不同的设备特性分为不同的版本。对于图片资源,会根据设备所需图片分辨率不同分发给对应设备所需对应的图片资源。

主要是图片资源的Slicing,我们有自己的方案,没有采用

On Demand Resources

App的资源只有要使用时才下载,如果其他资源需要空间这些资源可以被移除

更适合游戏类App,项目没有使用

Bitcode

Bitcode可以作为中间产物一起提交AppStore。包含Bitcode配置的程序将会在AppStore上被编译和链接。Bitcode允许苹果在后期重新优化我们程序的二进制文件,而不需要我们重新提交一个新的版本到AppStore上

使用BitCode的要求所有代码都支持BitCode,改动项目较大,没有使用

说明:可以充分利用App Slicing实现图片资源的瘦身

  • 在项目中引入图片时候,直接在 Assets.xcassets中添加就可以(资源文件用Asset Catalog管理),这样能使用到App Slicing功能,这样当用户从App Store上下载App时,可以只下载适用于其设备的App架构版本和所需资源,从而减少App所占的空间。
  • 在实践中发现,有的新同学在Assets.xcassets中引入@1x的图片,iPhone手机目前需要的@2x和@3x图片,所以@1x的图片显然是不需要的。
  • 在实践中还发现,有的图片资源游离在Assets.xcassets之外,这些可以考虑是否可以放入Assets.xcassets中(大部分情况下是可以放入的)
2、优化手段2:Xcode编译项
  • 因为绝大部分引入的图片是PNG格式,Xcode 提供的给我们两个编译选项来帮助压缩 PNG 资源:
  • Compress PNG Files:设置为YES,打包的时候自动对图片进行无损压缩,使用的工具为 pngcrush,压缩比还是相当高的,比较流行的压缩软件 ImageOptim 也是使用 pngcrush 进行压缩 PNG 的。
  • Remove Text Medadata From PNG Files:设置为YES,能帮助我们移除 PNG 资源的文本字符,比如图像名称、作者、版权、创作时间、注释等信息。
  • 引入项目的PNG资源自动被 Xcode 进行压缩了,但是如果是使用Bundle管理的资源,不会被Xcode压缩,可以使用tinypng压缩。
3、优化手段3:清理无用的资源
  • 及时清理不使用的图片资源。使用类似LSUnusedResources 清理旧的图片文件。
  • LSUnusedResources的思路是,先获取图片文件(imageset, jpg, png, gif)集合A,然后搜索代码文件中所有字符串名称得到B,然后从A集合中排除集合B就得到未使用的图片资源。
4、优化手段4:图片文件去重
  • 遍历图片文件,计算每个文件的MD5值,然后以MD5值为key,文件路径存入key对应的数组;
  • 遍历字典values,将value的数组大小大于1的路径输出,这样就找到重复图片的路径了。
5、优化手段5:更适合的图片格式
  • iconfont代替项目中纯色小图标,也省去很多@2x和@3x的图片切图。
  • PNG切图的替换方案,如PDF矢量图来代替大部分简单的png切图;然后在代码中自己解码并展示出来,一套PDF矢量图可以等效大部分2x和3x的png图片;
  • 网络图片选择压缩比更好的图片格式,如webp

说明:PNG切图不可能被完全替换,在表现颜色丰富图片时候,PNG效果很不错,其他详见浅谈iOS图片优化

三、可执行文件优化

1、优化手段1:编译器优化
  • Xcode 支持编译器层面的一些优化优化选项,可以让我们介于更快的编译速度、更小的二进制大小和更快的执行速度之间自由选择想要进行的优化粒度;
  • 在Xcode中,使用Clang来编译Objective-C,可以在 Build Setting -> Apple Clang - Code Generation -> Optimization Level 设置,Release下为Fastest Smallest[-Os]。编译器会开启除了会明显增加包大小以外的所有优化选项;
  • 在Xcode中,使用SwiftLang来编译Swift语言,同样也是基于 LLVM 后端的。Xcode 9.3 版本之后可以在Build Setting -> Optimization Level 设置,Release下为Optimize for Speed[-O],这可能会增加安装包大小
No optimization[-Onone]:不进行优化,能保证较快的编译速度。
Optimize for Speed[-O]:编译器将会对代码的执行效率进行优化,一定程度上会增加包大小。
Optimize for Size[-Osize]:编译器会尽可能减少包的大小并且最小限度影响代码的执行效率
复制代码

说明:Xcode 9.3/Swift4.1编译器不是特别稳定,特别是开启 Osize 选项之后,编译器很多情况下会莫名其妙的崩溃(Segmentation fault),目前放弃 [-Osize],选择[-O]

2、优化手段2:去除符号信息
  • 可执行文件中的符号:程序中的所有的变量、类、函数、枚举、变量和地址映射关系,以及一些在调试的时候使用到的用于定位代码在源码中的位置的调试符号,符号和断点定位以及堆栈符号化有很重要的关系。
  • Strip Style表示的是我们需要去除的符号的类型的选项,可以在Build Setting -> Strip Style设置, Release下为All Symbols,其分为三个选择项:
All Symbols: 去除所有符号,一般是在主工程中开启。

Non-Global Symbols: 去除一些非全局的 Symbol(保留全局符号,Debug Symbols 同样会被去除),链接时会被重定向的那些符号不会被去除,此选项是静态库/动态库的建议选项。

Debug Symbols: 去除调试符号,去除之后将无法断点调试。
复制代码

说明:iOS 的调试符号是 DWARF 格式的,使用 Xcode 编译打包的时候会先通过可执行文件的 Debug Map 获取到所有对象文件的位置,然后使用 dysmutil 来将对象文件中的 DWARF 提取出来生成 dSYM 文件。

  • Strip Linked Product去除不必要的符号信息,去除了符号信息之后我们就只能使用 dSYM 来进行符号化了,所以需要将 Debug Information Format 修改为 DWARF with dSYM file。Release下为YES。
  • Strip Linked Product 选项在 Deployment Postprocessing 设置为 YES 的时候才生效,而在 Archive 的时候 Xcode 总是会把 Deployment Postprocessing 设置为 YES,Debug下,Deployment Postprocessing 设置为 NO。
  • Strip Debug Symbols During Copy将那些拷贝进项目包的三方库、资源或者 Extension 的 Debug Symbol 去除掉,在Build Settings -> Strip Debug Symbols During Copy设置,Release下设置为YES。
  • Cocoapods 管理的动态库(use_framework!)的情况就相对要特殊一点,因为 Cocoapods 中的的动态库是使用自己实现的脚本 Pods-xxx-frameworks.sh 来实现拷贝的,所以并不会走 Xcode 的流程,当然也就不受 Strip Debug Symbols During Copy 的影响。当然 Cocoapods 是源码管理的,所以只需要将源码 Target 中的 Strip Linked Product 设置为 YES 即可。
  • Strip Swift Symbols能帮助我们移除相应 Target 中的所有的 Swift 符号,这个选项也是默认打开的。Strip Swift symbols需要在打包的发布选项中勾选(默认勾选),在Swift ABI 稳定之前,Swift 标准库是会打进目标文件的。
3、优化手段3:BitCode
  • Bitcode可以作为中间产物一起提交AppStore。包含Bitcode配置的程序将会在AppStore上被编译和链接。Bitcode允许苹果在后期重新优化我们程序的二进制文件,而不需要我们重新提交一个新的版本到AppStore上。
  • 开启 BitCode 之后编译器后端(Backend)的工作都由 Apple 接管了。所以假如以后苹果推出了新的 CPU 架构或者以后 LLVM 推出了一系列优化,我们也不再需要为其发布新的安装包了。
  • 工程开启 BitCode 之后必须要求所有打进 Bundle 的 Binary 都需要支持 BitCode,也就是说我们依赖的静态库和动态库都是含有 BitCode 的,不然就会打包失败。对于 Cocoapods 等源码管理工具来管理的依赖库来说操作会比较简单,我们只需要开启 Pods 工程中的 BitCode 就行。但是对于一些三方的闭源库,我们就无能为力了。
  • 开启 BitCode 之后,由于最终的可执行文件是 Apple 自动生成的,同时产生新的符号表文件,所以我们使用原本打包生成的 dSYM 符号化文件是无法完成符号化的。所以我们需要在上传至 App Store 时需要勾选 Include app symbols for your application to receive symboilcated crash logs from Apple:勾选之后 Apple 会给我们生成 dSYM,然后就可以在 Xcode -> Organizer 或者 iTunes Connect 中下载对应的 dSYM 来进行符号化了。
4、优化手段4:清除无用代码
  • Dead Code Stripping:Xcode 默认会开启此选项,C/C++/Swift 等静态语言编译器会在 link 的时候移除未使用的代码,但是对于 Objective-C 等动态语言是无效的。因为 Objective-C 是建立在运行时上面的,底层暴露给编译器的都是 Runtime 源码编译结果,所有的部分应该都是会被判别为有效代码。
  • 扫描查找无用代码:基本思路都是查找已经使用的方法/类和所有的类/方法,然后从所有的类/方法当中剔除已经使用的方法/类剩下的基本都是无用的类/方法,但是由于 Objective-C 是动态语言,可以使用字符串来调用类和方法,所以检查结果一般都不是特别准确,需要二次确认。目前市面上的扫描的思路大致可以分为 3 种:
  • 基于 Clang 扫描
  • 基于可执行文件扫描
  • 基于源码扫描
  • 及时下线不需要的功能,如完成使命的ABTest代码、被产品抛弃的功能代码等。
  • 移除不需要的系统库和第三方库。
5、优化手段5:代码重构
  • 功能合并:相似功能的代码,只需维护一份就可以了。如定制通用UI组件,大家可以有类似需求,可以给通用UI组件的开发提,没必要自己单独实现。

End

1、优化之后
  • 保持良好的开发习惯。及时清理无用代码和无效库
  • 持续关注安装包大小的变化,
  • 定期Review安装包大小变化
  • 建议预警机制,监控每个版本的体积大小,体积图片突然变大,要去找原因。