iOS 优化 - 瘦身
Posted CoderStar
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 优化 - 瘦身相关的知识,希望对你有一定的参考价值。
Hi Coder,我是 CoderStar!
ios 优化将是一个专题,其中会包括包体积优化(瘦身)、启动时间优化、UI 优化等等。那么这个专题的开篇就从瘦身开始吧。
APP 的大小是分为 APP 下载大小和安装大小两个概念的。
用户在商店看到的大小是安装大小。如果想看安装包在各机型上的下载、安装大小可以在 App Store Connect 后台查看。
App Store OTA 下载大小限制:
虽然苹果历年都会调整 App 下载大小,由之前的 100M 到后来的 150M 再到现在的 200M。如今,App 下载大小超出 200 MB 时 ,会出现两种情况:
Apple __TEXT 段大小限制:
顺便给大家说下苹果将下载大小限制由 100M 调整到 150M 的原因是什么?主要原因就是 Uber 当年用 Swift 重构开发 APP 时,随着业务的增长,后期发现实在无法再将 APP 尺寸降到 100M 以下,只能联系苹果让其将下载大小提升到 150M,同时苹果的 Swift 团队还帮助添加了一些编译器选项 (-Osize)。
该文主要研究的是如何降低 APP 的下载大小的,因为文章篇幅较长,如果大家不想细读,可以直接跳过细节展开看每个小节的结论部分。
将 ipa 安装包后缀名改为 zip,将其解压,显示.app 包内容后,就可以很直观的看到安装包的组成部分。一般会包括以下几个部分:
其实核心组成部分便是资源文件与Mach-O 可执行文件两部分,这两个部分便是我们的主要瘦身方向。在瘦身过程中,应该尽量使用 ROI 最高的优化手段,付出更少的精力,得到更多的收益。
在介绍我们作为开发者的优化方向之前,我们先看一下苹果自身对于 APP 下载大小的优化有哪些吧,我们要充分利用 Apple 自身的优化机制。
App Thinning 是指 iOS9 以后引入的一项优化,Apple 会尽可能,自动降低分发到具体用户时所需要下载的 App 大小。其主要包含以下三项功能。
管理,如果直接放在 Bundle 中,则不会被优化。关于 Asset Catalog 相关知识点及优化结论可见下文 Assets Catalog 章节。
总结:尽量将图片等资源交给Asset Catalog
管理。
如果想对 Bitcode 了解更深入一些,可以看下我之前的一篇博文--iOS 编译简析。
结论:可根据项目实际情况决定是否开启,如果项目混编了 Flutter、依赖的部分库不支持 Bitcode 以及不想处理一遍 DYSM 符号化,就不要进行开启,否则可以选择开启。
文件夹中的资源,起到减小下载安装包尺寸的目的。结论:该方式与下文提到的资源远程化本质一样,只不过一个是放在自己服务器,一个是放在苹果服务器,可根据自己项目实际情况选择是否使用。
资源文件优化方向比较多,相对优化 Mach-O 可执行文件来讲,风险也比较小。
进行安装,可以使用fdupes -Sr 文件夹名称
来查看所有涉及到的目录和子目录中的重复文件的大小,其余相关指令可自行查阅,不建议使用 fdupes 相关命令直接删除搜索出来的重复资源,风险比较高。结论:考虑到工具的不准确性,可以利用工具粗检测一下哪些资源没有被使用,然后经人工确认后才统一进行删除。对于工具无法检测出来的资源,就只能人工进行筛查了,可每人分配几个模块,提高效率。
提供的给我们两个编译选项来帮助压缩 PNG 资源 。Remove Text Medadata From PNG Files
(默认开启):能帮助我们移除 PNG 资源的文本字符,比如图像名称、作者、版权、创作时间、注释等信息。Compress PNG Files
(默认开启):当设置为 YES 后,打包的时候会利用 pngcrush
工具自动对项目中所有 PNG 图片进行无损压缩以及修改文件格式,该工具是开源的--设置为 YES 后,XCode 会调用该路径的脚本/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/iphoneos-optimize
。
pngcrush
工具在其同级目录存放,iphoneos-optimize 脚本中关于 PNG 压缩的内容如下:
工具进行处理。我们可以通过下面命令手动使用 pngcrush
工具。
工具编码结果主要变化内容如下:在 IHDR 块之前插入了 CgBI 块来表示这种格式 修改 IDAT 块中的数据,去除 zlib 压缩头和 Adler-32 校验和; 八位真彩色图像按 BGR/BGRA 顺序存储,而不是按 IHDR 块中指示的 RGB 和 RGBA 顺序存储; 图像像素使用预乘 alpha; 修改后的文件使用。png 为有效图像定义的文件扩展名以及内部文件结构,但符合 PNG 的查看和编辑软件不再能够处理它们; 增加了一个 iDot 数据块,是 Apple 自定义的数据块,暂时不知其作用; 其本质是使正常的 png 图片变成了一个优化后的 CgBI 格式的 png。可以利用工具编码过程并不是简单的压缩数据,更重要的是对文件格式做了修改。因为 iPhone 中,图像是以 BGRA 格式在内存中处理的,所以修改后的格式变成了 iPhone 能更方便处理的格式,加快处理速度。
根据我自己测试的压缩效果来看,对于 Bundle 中放置的 png 图片,经过 pngcrush
的处理,大小不降反增,目前暂时没有找到哪些具体因素影响其压缩效果。
结论:Compress PNG Files 虽然是压缩 PNG,但其最主要的目的并不是为了压缩图片大小, 而是将 PNG 转换成 iOS 更容易处理、更快速度的去识别的格式,可以根据项目在开启、关闭两种情况下的打包大小,自行取舍。
非 PNG 资源
非 PNG 资源压缩包含两种方式:
直接通过一些压缩工具将资源进行压缩,格式保持不变,如一些图片资源、音视频资源等,图片压缩工具下文会有介绍。 还有一些文本资源,如 json 文件、html 文件等,无法使用上述的方式压缩,可以采用压缩成 zip 等压缩格式的方式,可分为三步: 压缩阶段:在 Build Phase 中添加脚本,构建期间对白名单内的文本文件做 zip 压缩; 解压阶段:在 App 启动阶段,在异步线程中进行解压操作,将解压产物存放到沙盒中; 读取阶段:在 App 运行时,hook 读取这些文件的方法,将读取路径从 Bundle 改为沙盒中的对应路径;
结论:可选用合适的压缩工具对音视频、非 Assets Catalog 管理的图片资源进行压缩。对于一些比较大的文本文件可选用第二种运行时解压读取的方式,如 Lottie 动画的 json 文件。
节点时, 构建 Asset Catalog 的工具 actool
会首先对 Asset Catalog 中的 png 图片进行解码,得到 Bitmap 数据,然后再运用 actool
的编码压缩算法进行编码压缩处理。如果你开发时放入到 Assets 中的是 jpg 格式文件,在最终生成的 Assets.car 文件中也会成为 png 图片。
xcrun assetutil --info Assets.car
。可使用该命令检查 Assets.car 中每张图片使用的编码压缩算法。
目前 actool
会使用的压缩算法包括 lzfse
、 palette_img
、 deepmap2
、 deepmap_lzfse
、 zip
,影响其使用何种算法的因素包括 iOS 系统版本、ASSETCATALOG_COMPILER_OPTIMIZATION 设置(位于 Build Setting 中)等;
iOS 11.x 版本:对应的压缩算法为 lzfse、zip; iOS 12.0.x - iOS 12.4.x: 对应的压缩算法为 deepmap_lzfse、palette_img; iOS 13.x: 对应的压缩算法为 deepmap2 ; 按照压缩比来讲 lzfse < palette_img ~= deepmap_lzfse < deepmap2
如果设置了 ASSETCATALOG_COMPILER_OPTIMIZATION
为 space
那么在低版本 iOS 系统上,使用 lzfse
压缩算法的图片会变成 zip
的算法,可减少 iOS 11.x 及以下的 iOS 设备图片的占用大小。其他 iOS 版本的压缩算法不受这个配置的影响。
无损压缩通过变换图片的编码压缩算法减少大小,但是不会改变 Bitmap 数据,对于 actool
来说,它接收的输入(Bitmap 数据)没有改变,所以无损压缩无法优化 Assets.car 的大小,但是可以用来优化非 Asset Catalog 管理的图片。
使用有损压缩方式并采用合适的压缩方法是可以减小 Assets.car 的大小。可以对图片采用RGB with palette
(调色板算法)编码方式来达到图标压缩的效果,这种编码方式进行压缩特别适合内部颜色相对接近的图标。但是需要注意如果图片中有半透明效果,这种压缩方式可能会导致半透明的地方出现噪点,所以压缩之后请注意仔细检查一下。
RGB with palette 编码的得到的字节流首先维护了一个颜色数组。颜色数组每个成员用 RGBA 四个分量维护一个颜色。图像中的每个像素点则存储颜色数组的下标代表该点的颜色。颜色数组维护的颜色种类和数量由图片决定,同时可以人为的限制颜色数组维护颜色的种类的上限,默认为最大值 256 种,具体原理详见底部相关链接 --【Palette Images】;
使用下文提到的 ImageOptim-CLI 工具,我们可以改变图片的编码方式为 RGB with palette
,命令如下:
imageoptim -Q --no-imageoptim --imagealpha --number-of-colors 16 --quality 40-80 ./1.png
--number-of-colors:控制颜色数组维护颜色的数量;--quality:控制图片的质量变为原来的百分比;命令中的数值可以在显著减少包大小的同时维持肉眼看不到的质量变化。
以图片资源举例,我们可以使用工具对其进行压缩,推荐几款工具如下:
等工具,实现中间过程自动化。 如果想将 car 文件中的 png 提取出来,可以使用方式将部分资源放在苹果服务器之外,我们也可以将一些本地资源转移到自己的服务器上去。这样不仅降低了安装包大小,也将这些资源动态化了。适合放在服务器的资源应包含以下几个特性:
不影响首屏加载体验; 变化频率较高; 尺寸很大; 如一些 Banner 广告图、主题资源、音视频资源、H5 资源资源。
结论:可尽量将满足上述特性的资源放置在服务器。
Xcode 支持编译器层面的一些优化选项,通过修改 Build Setting
的一些相关配置,可以让我们介于更快的编译速度和更小的二进制大小并且更快的执行速度之间自由选择想要进行的优化粒度,这些选项有的会影响资源文件,有的会影响可执行文件,因为内容比较多,所以起一个独立的章节描述。
这种方式的性价比很高,改动一项配置,就可能会带来收益,但是可能具有一定的风险,需要谨慎。
下文中提到的一些 Xcode 默认配置可能在低版本 Xcode 上不是默认配置,如果不是默认,可手动勾选。
- Excluded Architectures
项设置排除的架构。先看一下几种架构的含义:
模拟器 32 位处理器测试需要 i386
架构; 模拟器 64 位处理器测试需要 x86_64
架构; 真机 32 位处理器需要 armv7
, 或者 armv7s
架构; 真机 64 位处理器需要 arm64
架构。 armv6 armv7 armv7s arm64 iPhone
iPhone2
iPhone3G
第一代和第二代 iPod Touch iPhone4
iPhone4S
iPad1-iPad3,3、4 代 iPod Touch
iPad mini iPhone5
iPhone5C
iPad4 iPhone 5S 等剩余全部机型
结论:理论上只保留 arm64 架构其实就够用了,可以去除 armv6
、 armv7
、 armv7s
三种架构。
- Link-Time Optimization
项设置优化方式
其提供三种选项:
No
不开启链接期优化;(默认项)Monolithic
生成单个 LTO 文件,每次链接重新生成,无缓存高内存消耗,参数 LLVM_LTO=YES;Incremental
生成多个 LTO 文件,增量生成,低内存消耗,参数 LLVM_LTO=YES_THIN;LTO 能带来的优化有:
将一些函数內联化:不用进行调用函数前的压栈、调用函数后的出栈操作,提高运行效率与栈空间利用率; 去除了一些无用代码:如果一段代码分布在多个文件中,但是从来没有被使用,普通的 -O3 优化方法不能发现跨中间代码文件的多余代码,因此是一个局部优化。但是 Link-Time Optimization
技术可以在 link 时发现跨中间代码文件的多余代码; 对程序有全局的优化作用:这是一个相对广泛的概念。举个例子来说,如果一个 if 方法的某个分支永不可能执行,那么在最后生成的二进制文件中就不应该有这个分支的代码。 LTO 会降低编译链接的速度,所以建议在打正式包时开启;开启了 LTO 之后,Link Map 的可读性明显降低,多出了很多数字开头的类(LTO 的全局优化导致的),所以如果需要阅读 Link Map,可以先关闭 LTO;
LTO 虽然是链接期优化,但是仍然需要编译期参与,加入了 LTO 的编译出来的 .a 本质是 LLVM 的 BitCode,如果使用未开启 LTO 构建出来的的 .a 直接是机器码了。直接链接是无法完成 LTO 优化的。
开启 LTO 之后跨编译单元的重复代码会被链接器单独生成以 .lto.o 为后缀的目标文件进行链接。尤其是对于 Objc Runtime 需要的一些结构, 比如方法签名的 literal string、protocol 的结构等有比较大的优化。同时开启 Oz 和 LTO 可以让外联函数都只存在一份能够最大限度的优化安装包体积(是全局的优化作用,将已经外联的函数去重)。如果项目中大量的使用了 Protocol 建议还是开启这个选项。
结论:可将Link-Time Optimization
选项由 NO
改为 Incremental
。
-> Apple Clang - Code Generation
-> Optimization Level
,选项如下:None[-O0]: 编译器不会优化代码,意味着更快的编译速度和更多的调试信息,默认在 Debug 模式下开启; Fast[-O, O1]: 编译器会优化代码性能并且最小限度影响编译时间,此选项在编译时会占用更多的内存; Faster[-O2]:编译器会开启不依赖空间 / 时间折中所有优化选项。在此,编译器不会展开循环或者函数内联。此选项会增加编译时间并且提高代码执行效率; Fastest[-O3]:编译器会开启所有的优化选项来提升代码执行效率。此模式编译器会执行函数内联使得生成的可执行文件会变得更大。一般不推荐使用此模式; Fastest Smallest[-Os]:编译器会开启除了会明显增加包大小以外的所有优化选项。默认在 Release 模式下开启; Fastest, Aggressive Optimization[-Ofast]:启动 -O3 中的所有优化,可能会开启一些违反语言标准的一些优化选项。一般不推荐使用此模式。 结论:使用默认配置即可,无需修改。
Swift
Swift 关于编译内联优化的参数位于 Build Settings
-> Swift Compiler - Code Generation
-> Optimization Level
,可选参数如下。
No optimization[-Onone]:不进行优化,能保证较快的编译速度。默认在 Debug 模式开启; Optimize for Speed[-O]:编译器将会对代码的执行效率进行优化,一定程度上会增加包大小。默认在 Release 模式下开启; Optimize for Size[-Osize]:编译器会尽可能减少包的大小并且最小限度影响代码的执行效率。 Optimize for Size
的核心原理是对重复的连续机器指令外联成函数进行复用,和函数内联的原理正好相反。因此,将其开启,能减小二进制的大小,但同时理论上会带来执行效率的额外消耗,对性能(CPU)敏感的代码使用需要评估。
具体官方描述可见和 Whole Module
同时开启效果会发挥的最好,从现有的案例中可以看到它会减少 5%~30% 的可执行文件大小,并且对性能的影响也微乎其微(大约 5%)。
结论:将 Release 默认下配置改为 Optimize for Size[-Osize]
,Compliation Mode
选项改为Whole Module
- DEAD_CODE_STRIP
项设置。在构建完成之后如果是 C、C++ 等静态的语言的代码、一些常量定义,如果发现没有被使用到将会被标记为 Dead code。开启 DEAD_CODE_STRIP = YES
这些 Dead code 将不会被打包到安装包中。在 LinkMap 这些符号也会被标记为 <<dead>>
。
该项其实也属于在清除无用代码。
结论:默认配置即为 YES,所以使用默认配置即可,无需修改。
表示的是我们需要去除的符号的类型的选项,其分为三个选择项:All Symbols: 去除所有符号,一般是在主工程中开启; Non-Global Symbols: 去除一些非全局的 Symbol(保留全局符号,Debug Symbols 同样会被去除),链接时会被重定向的那些符号不会被去除,此选项是静态库 / 动态库的建议选项; Debug Symbols: 去除调试符号,去除之后将无法断点调试。 结论:主工程选择All Symbols
,静、动态库选择Non-Global Symbols
。
来去除不需要的符号信息 (Strip Style 中选择的选项相应的符号),去除了符号信息之后我们就只能使用 dSYM
来进行符号化了,所以需要将 Debug Information Format
修改为 DWARF with dSYM file
。需要注意Strip Linked Product
选项在 Deployment Postprocessing
设置为 YES 的时候才生效,而 Deployment Postprocessing 在 Archive 时不受手动设置的影响,会被强制设置成 YES。
结论:将Deployment Postprocessing
设置为 NO,将Strip Linked Product
设置为YES
,将Release
模式的下的Debug Information Format
修改为 DWARF with dSYM file
。
类似,但是这个是将那些拷贝进项目包的三方库、资源或者 Extension 的 Debug Symbol
去除掉,同样也是使用的 strip 命令。这个选项不受Deployment Postprocessing
的控制,所以我们只需要在 Release 模式下开启,不然就不能对三方库进行断点调试和符号化了。Cocoapods 管理的动态库 (use_framework!) 的情况就相对要特殊一点,Cocoapods 中的的动态库是使用自己实现的脚本 Pods-xxx-frameworks.sh 来实现拷贝的,所以并不会走 Xcode 的流程,当然也就不受 Strip Debug Symbols During Copy 的影响。当然 Cocoapods 是源码管理的,所以只需要将源码 Target 中的 Strip Linked Product 手动设置为 YES 即可。
结论:Strip Debug Symbols During Copy
在Release
模式下设置为YES
,在Debug
模式下设置为false
。
能帮助我们移除相应 Target 中的所有的 Swift 符号,这个选项也是默认打开的。这一选项是出现 Xcode 将 xcarchive 包导出成 ipa 文件过程中出现的,不是通过Build Setting
设置的。结论:一般默认勾选,如果没勾选请手动勾选。
或者 pod update
会重置,所以需要 hook pod install
来设置 Pods 中每个 Target 的编译选项。在对 Mach-O 文件进行瘦身优化时,我们可以通过分析 Link Map 文件来给我们一定的数据参考,帮助我们分析 Mach-O 文件的构成。
Link Map 是编译链接时可以生成的一个 txt 文件,它生成目的就是帮助程序员分析包大小。Link Map 记录了每个方法在当前的二进制架构下占据的空间。通过分析 Link Map,我们可以了解每个类甚至每个方法占据了多少安装包空间。
开启 Build setting
中的 Write Link Map File
开关,Xcode 就会生成一份 Link Map 文件。其中生成的 Link Map 文件路径如下:~/Developer/Xcode/DerivedData/项目/Build/Intermediates.noindex/项目.build/Debug-iphonesimulator/项目.build/项目-LinkMap-normal-x86_64.txt
如果直接阅读 Link Map 文件,效率会比较低,也不直观,我们可以使用一些工具帮助我们分析。
配合 xcassets 的方式来集成各个插件中的资源文件,因为 resource_bundle
中的资源在构建期能经过 Xcode 的优化,而 resource 中的资源则不能。并且这种形式可以将每个 pod 的资源放在自己的 Bundle 中,更方便管理。
结论:自定义 Pod 如果含有资源,尽量使用 resource_bundle 的方式引用资源。
本文主要归纳总结了一些常用的瘦身方法,当然不同的项目需求以及业务场景都会产生一些对应的瘦身方法,大家可以根据自己的业务特性去寻找一些更好更优的瘦身技巧。
最后,祝大家周末愉快!
Let\'s be CoderStar!
相关链接
我在 Uber 亲历的最严重的工程灾难[19] iOS 安装包瘦身实践[20] 今日头条 iOS 安装包大小优化 - 新阶段,新实践[21] 干货|今日头条 iOS 端安装包大小优化—思路与实践 今日头条优化实践:iOS 包大小二进制优化,一行代码减少 60 MB 下载大小 探究 WebP 一些事儿[22] Palette Images[23] iOS PNG 使用指南 iOS 减包实战:Compress PNG Files 作用分析[24] iOS App 瘦身减肥记[25] iOS 安装包瘦身 (上篇)[26] iOS 安装包瘦身(下篇)[27] 参考资料[1]LSUnusedResources: https://github.com/tinymind/LSUnusedResources
[2]FengNiao: https://github.com/onevcat/FengNiao.git
[3]Duplicate Photos: https://www.duplicatephotocleaner.com/
[4]R.swift: https://github.com/mac-cain13/R.swift
[5]fdupes: https://github.com/adrianlopezroche/fdupes
[6]pngcrush 地址: https://github.com/Kjuly/pngcrush
[7]pngcheck: http://www.libpng.org/pub/png/apps/pngcheck.html
[8]pngdefry: http://www.jongware.com/pngdefry.html
[9]TinyPng: https://tinypng.com/
[10]TinyPNG4Mac: https://github.com/kyleduo/TinyPNG4Mac/
[11]ImageOptim: https://github.com/ImageOptim/ImageOptim
[12]ImageOptim-CLI: https://github.com/JamieMason/ImageOptim-CLI
[13]Asset Catalog Tinkerer: https://github.com/insidegui/AssetCatalogTinkerer
[14]iSpart: http://isparta.github.io/
[15]webp 工具: https://developers.google.com/speed/webp/docs/using
[16]Code Size Optimization Mode in Swift 4.1: https://swift.org/blog/osize/
[17]LinkMap 工具地址: https://github.com/huanxsd/LinkMap
[18]fui: https://github.com/dblock/fui
[19]我在 Uber 亲历的最严重的工程灾难: https://www.infoq.cn/article/asjhHAmupqtcx5oGrb4b
[20]iOS 安装包瘦身实践: https://xiaozhuanlan.com/topic/6147250839
[21]今日头条 iOS 安装包大小优化 - 新阶段,新实践: https://zhuanlan.zhihu.com/p/358002160
[22]探究 WebP 一些事儿: https://jelly.jd.com/article/6006b1035b6c6a01506c87a9
[23]Palette Images: http://www.manifold.net/doc/mfd9/palette_images.htm
[24]iOS 减包实战:Compress PNG Files 作用分析: https://cloud.tencent.com/developer/article/1368027
[25]iOS App 瘦身减肥记: https://juejin.cn/post/6919751122760679437?utm_source=gold_browser_extension#heading-3
[26]iOS 安装包瘦身 (上篇): https://sq.163yun.com/blog/article/200385709022117888
[27]iOS 安装包瘦身(下篇): https://sq.163yun.com/blog/article/200384401846304768
有一个技术的圈子与一群志同道合的朋友非常重要,来我的技术公众号及博客,这里只聊技术干货。
微信公众号:CoderStar
Android 性能优化--apk瘦身优化
apk瘦身
瘦身优化及apk分析方案介绍
瘦身优势
1、下载转换率的提升
2、头部App都有Lite版本
3、渠道合作商要求
apk的组成
1、代码相关:classes.dex
2、资源相关:res、asserts、resources.arsc
3、so相关:lib
apk分析
1、ApkTool反编译工具(官网:https://ibotpeaches.github.io/Apktool/)
命令:apktool d xx.apk
以上是关于iOS 优化 - 瘦身的主要内容,如果未能解决你的问题,请参考以下文章