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 大小。其主要包含以下三项功能。
当向 App Store Connect 上传 .ipa 后,App Store Connect 构建过程中,会自动分割该 App,会专门针对不同的设备来选择只适用于当前设备的内容(主要是架构和资源)以供设备下载。其差异性主要是体现在架构(32 位还是 64 位)和资源(@1x、@2x 还是 @3x)等方面上。
其中架构方面开发者不需要去控制,但是对于资源来说要求图片在 Asset Catalog 管理,如果直接放在 Bundle 中,则不会被优化。
关于 Asset Catalog 相关知识点及优化结论可见下文 Assets Catalog 章节。
总结:尽量将图片等资源交给Asset Catalog管理。
Bitcode 是一个编译好的程序的中间表示形式(IR)。上传到 App Store Connect 中的包含 Bitcode 的 App 将会在 App store 中进行链接和编译。苹果会对包含 Bitcode 的二进制 app 进行二次优化,而不需要提交一个新的 app 版本到 app store 中。属于 Apple 内部的优化,但需要注意;
开启方式:Build Settings -> Enable Bitcode -> 设置为 YES 。
如果想对 Bitcode 了解更深入一些,可以看下我之前的一篇博文--iOS 编译简析。
结论:可根据项目实际情况决定是否开启,如果项目混编了 Flutter、依赖的部分库不支持 Bitcode 以及不想处理一遍 DYSM 符号化,就不要进行开启,否则可以选择开启。
On-Demand Resource 即一部分图片可以被放置在苹果的服务器上,不随着 App 的下载而下载,直到用户真正进入到某个页面时才下载这些资源文件。
应用场景:相机应用的贴纸或者滤镜、关卡游戏等。
开启方式:Build Settings -> Enable On Demand Resources -> 设置为 YES(默认开启)。
设置资源的 Tag 类型,种类包括:
如果项目中有 Demand Resources,则最后生成的安装包结构大致层级为:
具体使用方法这里就不展开讲了。
我们在下载安装包时,不会下载 OnDemandResources 文件夹中的资源,起到减小下载安装包尺寸的目的。
结论:该方式与下文提到的资源远程化本质一样,只不过一个是放在自己服务器,一个是放在苹果服务器,可根据自己项目实际情况选择是否使用。
资源文件优化方向比较多,相对优化 Mach-O 可执行文件来讲,风险也比较小。
业务的迭代开发,出现无用的图片资源是比较正常的,我们可以借助工具找出哪些图片资源没有被使用过。推荐下面两款工具:
因为这类工具的原理都是在相关文件(.m、.swift 等等)中利用正则表达式检测是否有图片名称的字符,所以存在以下问题。问题点:
可以利用Duplicate Photos[3]从内容上检测重复/相似图片。
引申一下:
之所以要使用自动化工具来检测重复资源的原因是因为资源是 弱类型,我们在项目迭代过程 中手动去维护是相当麻烦的一个过程。转换一下思维,如果资源变成强类型了, 那我们维护起来就相当容易了。目前就有这样一个工具 R.swift[4]一定意义上将资源变成强类型, 类似于 Android 开发中的 R 文件。
可利用fdupes[5]查找项目中的重复文件。其原理是对比不同文件的签名,签名相同的文件就会判定为重复资源。
mac 上可直接通过 brew install fdupes 进行安装,可以使用 fdupes -Sr 文件夹名称 来查看所有涉及到的目录和子目录中的重复文件的大小,其余相关指令可自行查阅,不建议使用 fdupes 相关命令直接删除搜索出来的重复资源,风险比较高。
结论:考虑到工具的不准确性,可以利用工具粗检测一下哪些资源没有被使用,然后经人工确认后才统一进行删除。对于工具无法检测出来的资源,就只能人工进行筛查了,可每人分配几个模块,提高效率。
请注意:这里的资源不包括 Assets Catalog 管理的资源。
PNG 资源
这一部分涉及前因后果比较多,为保证大家能看懂,会先铺垫一些原理性知识,请耐心阅读。
Xcode 的 Build Setting 提供的给我们两个编译选项来帮助压缩 PNG 资源 。
Remove Text Medadata From PNG Files(默认开启):能帮助我们移除 PNG 资源的文本字符,比如图像名称、作者、版权、创作时间、注释等信息。Compress PNG Files(默认开启):当设置为 YES 后,打包的时候会利用 pngcrush 工具自动对项目中所有 PNG 图片进行无损压缩以及修改文件格式,该工具是开源的--pngcrush 地址[6]。
Compress PNG Files 设置为 YES 后,XCode 会调用该路径的脚本/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/iphoneos-optimize。
pngcrush 工具在其同级目录存放,iphoneos-optimize 脚本中关于 PNG 压缩的内容如下:
sub optimizePNGs {
my $name = $File::Find::name;
if ( -f $name && $name =~ /^(.*)\.png$/i) {
my $crushedname = "$1-pngcrush.png";
// $PNGCRUSH便是pngcrush工具路径
my @args = ( $PNGCRUSH, "-q", "-iphone", "-f", "0" );
if ( $stripPNGText ) {
push ( @args, "-rem", "text" );
}
push ( @args, $name, $crushedname );
if (system(@args) != 0) {
print STDERR "$SCRIPT_NAME: Unable to convert $name to an optimized png!\n";
return;
}
unlink $name or die "Unable to delete original file: $name";
rename($crushedname, $name) or die "Unable to rename $crushedname to $name";
print "$SCRIPT_NAME: Optimized PNG: $name\n";
}
}
从内容上来看,脚本是通过png 后缀名来判断是否为 png 图片,如果图片改变后缀名,则该图片则不会被 pngcrush 工具进行处理。
我们可以通过下面命令手动使用 pngcrush 工具。
# image.png 经编码后 生成 image1.png
xcrun -sdk iphoneos pngcrush -iphone -f 0 image.png image1.png
# image.png 经解码后 还原成 image1.png
# 即使还原回去与原图片也不会完全一致,这里就不展开描述了
xcrun -sdk iphoneos pngcrush -revert-iphone-optimizations image.png image1.png
pngcrush 工具编码结果主要变化内容如下:
其本质是使正常的 png 图片变成了一个优化后的 CgBI 格式的 png。可以利用pngcheck[7]查看处理前的图片信息,利用pngdefry[8]查看处理后的图片信息(其还可以将 CgBI 格式的 png 还原回去,这个功能跟 pngcrush 工具解码功能类似)。
从上述变化内容来看, pngcrush 工具编码过程并不是简单的压缩数据,更重要的是对文件格式做了修改。因为 iPhone 中,图像是以 BGRA 格式在内存中处理的,所以修改后的格式变成了 iPhone 能更方便处理的格式,加快处理速度。
根据我自己测试的压缩效果来看,对于 Bundle 中放置的 png 图片,经过 pngcrush 的处理,大小不降反增,目前暂时没有找到哪些具体因素影响其压缩效果。

pngcrsuh测试效果对比图
结论:Compress PNG Files 虽然是压缩 PNG,但其最主要的目的并不是为了压缩图片大小, 而是将 PNG 转换成 iOS 更容易处理、更快速度的去识别的格式,可以根据项目在开启、关闭两种情况下的打包大小,自行取舍。
非 PNG 资源
非 PNG 资源压缩包含两种方式:
结论:可选用合适的压缩工具对音视频、非 Assets Catalog 管理的图片资源进行压缩。对于一些比较大的文本文件可选用第二种运行时解压读取的方式,如 Lottie 动画的 json 文件。
Assets Catalog 涉及的技术点比较多,后续可能会单独开一篇博文专门讲这一部分内容。
@1x 图是 iPhone 3Gs 用的,iPhone 4 开始使用 @2x 图了,iPhone 6p 开始使用 3x 图。
结论:可以删除 Assets 中所有 @1x 的图片资源。
Assets.car 文件是工程中 Asset Catalog 的构建产物。Xcode 构建过程中,在 compile asset catalog 节点时, 构建 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 中)等;
按照压缩比来讲 lzfse < palette_img ~= deepmap_lzfse < deepmap2
如果设置了 ASSETCATALOG_COMPILER_OPTIMIZATION 为 space 那么在低版本 iOS 系统上,使用 lzfse 压缩算法的图片会变成 zip 的算法,可减少 iOS 11.x 及以下的 iOS 设备图片的占用大小。其他 iOS 版本的压缩算法不受这个配置的影响。
actool 来说,它接收的输入(Bitmap 数据)没有改变,所以无损压缩无法优化 Assets.car 的大小,但是可以用来优化非 Asset Catalog 管理的图片。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:控制图片的质量变为原来的百分比;命令中的数值可以在显著减少包大小的同时维持肉眼看不到的质量变化。
以图片资源举例,我们可以使用工具对其进行压缩,推荐几款工具如下:
brew install imageoptim-cli安装,其会根据你的指定,选择性调用 JPEGmini、ImageAlpha、ImageOptim 等工具,实现中间过程自动化。如果想将 car 文件中的 png 提取出来,可以使用Asset Catalog Tinkerer[13]。
引申一下,好的工具是开发利器,之前整理了一些好用的 Mac 效率工具,可见Mac 效率软件。
结论:能用 Asset Catalogs 管理的资源,尽量使用 Asset Catalogs 来管理。使用 Asset Catalog 管理图片不要对图片进行无损压缩,最终起不到压缩效果,如果想要压缩,可以采用上面所提到的有损压缩方式,并检查压缩后的效果。
谷歌开源的格式,Webp 压缩率比较高,同时支持有损和无损两种压缩模式,可以带来更小的图片体积,而且肉眼看不出差异。根据 Google 的测试,无损压缩后的 WebP 比 PNG 文件少了 26%的体积,有损压缩后的 WebP 图片相比于等效质量指标的 JPEG 图片减少了 25%~34% 的体积。
但是 WebP 与 JPG 以及 PNG 相对比,在编解码的 CPU 消耗以及解码时间上会差一些,因为编码是用户上传图片时的一次性操作,并且编码过程是在服务器后台进行,对用户的影响不大,对用户影响大的主要是解码过程,会导致图片加载速度慢一些。所以,我们需要根据项目的实际情况在性能和体积上做取舍。
如果从服务器带宽以及流量来看,因为图片的体积变小,所以会减小带宽,降低成本。
推荐二种转 WebP 格式的方法
brew install webp;iOS 原生并不支持 WebP 格式加载,需要引入 SDWebImage/WebP,或者进行自研。
结论:该方案适合整个大前端及后端统一调整,整体进行优化,如果是单一的客户端进行调整,可能达不到最优效果。
除了上文提到的使用 On-Demand Resources 方式将部分资源放在苹果服务器之外,我们也可以将一些本地资源转移到自己的服务器上去。这样不仅降低了安装包大小,也将这些资源动态化了。适合放在服务器的资源应包含以下几个特性:
如一些 Banner 广告图、主题资源、音视频资源、H5 资源资源。
结论:可尽量将满足上述特性的资源放置在服务器。
结论:如果项目有相对的设计规范及标准图标样式,使用图标字体是一个很好的方案。剩余的优化点根据项目实际情况决定是否使用。
Xcode 支持编译器层面的一些优化选项,通过修改 Build Setting 的一些相关配置,可以让我们介于更快的编译速度和更小的二进制大小并且更快的执行速度之间自由选择想要进行的优化粒度,这些选项有的会影响资源文件,有的会影响可执行文件,因为内容比较多,所以起一个独立的章节描述。
这种方式的性价比很高,改动一项配置,就可能会带来收益,但是可能具有一定的风险,需要谨慎。
下文中提到的一些 Xcode 默认配置可能在低版本 Xcode 上不是默认配置,如果不是默认,可手动勾选。
可以在 Build Setting - Excluded Architectures 项设置排除的架构。
先看一下几种架构的含义:
i386 架构;x86_64 架构;armv7, 或者 armv7s 架构;arm64 架构。armv6 | armv7 | armv7s | arm64 |
|---|---|---|---|
iPhoneiPhone2iPhone3G第一代和第二代 iPod Touch | iPhone4iPhone4SiPad1-iPad3,3、4 代 iPod TouchiPad mini | iPhone5iPhone5CiPad4 | iPhone 5S 等剩余全部机型 |
结论:理论上只保留 arm64 架构其实就够用了,可以去除 armv6 、 armv7 、 armv7s 三种架构。
可以在
Build Setting-Link-Time Optimization项设置优化方式
其提供三种选项:
No 不开启链接期优化;(默认项)Monolithic 生成单个 LTO 文件,每次链接重新生成,无缓存高内存消耗,参数 LLVM_LTO=YES;Incremental 生成多个 LTO 文件,增量生成,低内存消耗,参数 LLVM_LTO=YES_THIN;LTO 能带来的优化有:
Link-Time Optimization 技术可以在 link 时发现跨中间代码文件的多余代码;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 。
OC
OC 关于编译内联优化的参数位于 Build Settings -> Apple Clang - Code Generation -> Optimization Level ,选项如下:
结论:使用默认配置即可,无需修改。
Swift
Swift 关于编译内联优化的参数位于 Build Settings -> Swift Compiler - Code Generation -> Optimization Level ,可选参数如下。
Optimize for Size 的核心原理是对重复的连续机器指令外联成函数进行复用,和函数内联的原理正好相反。因此,将其开启,能减小二进制的大小,但同时理论上会带来执行效率的额外消耗,对性能(CPU)敏感的代码使用需要评估。
具体官方描述可见Code Size Optimization Mode in Swift 4.1[16]
配合其使用的还有Compliation Mode设置,其含有两个选项
在 Relese 模式下 -Osize 和 Whole Module 同时开启效果会发挥的最好,从现有的案例中可以看到它会减少 5%~30% 的可执行文件大小,并且对性能的影响也微乎其微(大约 5%)。
结论:将 Release 默认下配置改为 Optimize for Size[-Osize],Compliation Mode选项改为Whole Module
可以在
Build Setting-DEAD_CODE_STRIP项设置。
在构建完成之后如果是 C、C++ 等静态的语言的代码、一些常量定义,如果发现没有被使用到将会被标记为 Dead code。开启 DEAD_CODE_STRIP = YES 这些 Dead code 将不会被打包到安装包中。在 LinkMap 这些符号也会被标记为 <<dead>> 。
该项其实也属于在清除无用代码。
结论:默认配置即为 YES,所以使用默认配置即可,无需修改。
可执行文件中的符号是指程序中的所有的变量、类、函数、枚举、变量和地址映射关系,以及一些在调试的时候使用到的用于定位代码在源码中的位置的调试符号,符号和断点定位以及堆栈符号化有很重要的关系。
Strip Style 表示的是我们需要去除的符号的类型的选项,其分为三个选择项:
结论:主工程选择All Symbols,静、动态库选择Non-Global Symbols。
并不是所有的符号都是必须的,比如 Debug Map,所以 Xcode 提供给我们 Strip Linked Product 来去除不需要的符号信息 (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。
与 Strip Linked Product 类似,但是这个是将那些拷贝进项目包的三方库、资源或者 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。
开启 Strip Swift Symbols 能帮助我们移除相应 Target 中的所有的 Swift 符号,这个选项也是默认打开的。这一选项是出现 Xcode 将 xcarchive 包导出成 ipa 文件过程中出现的,不是通过Build Setting设置的。

示意图
结论:一般默认勾选,如果没勾选请手动勾选。
大部分项目都会使用 Cocoapods 工具进行管理,Cocoapods 的 project 文件在每次 pod install 或者 pod update 会重置,所以需要 hook pod install 来设置 Pods 中每个 Target 的编译选项。
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
config.build_settings['STRIP_INSTALLED_PRODUCT'] = 'YES'
config.build_settings['SWIFT_COMPILATION_MODE'] = 'wholemodule'
if config.name == 'Debug'
config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Onone'
config.build_settings['GCC_OPTIMIZATION_LEVEL'] = '0'
else
config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Osize'
config.build_settings['GCC_OPTIMIZATION_LEVEL'] = 's'
end
end
end
end
在对 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 文件,效率会比较低,也不直观,我们可以使用一些工具帮助我们分析。
LinkMap 工具地址[17]

LinkMap效果图
AppCode 提供了非常强大的代码静态检查工具,使用 Inspect Code,可以找到很多代码优化的地方;

AppCode Inspect
一般都是对源码文件进行字符串匹配。例如将 A *a、[A xxx]、NSStringFromClass("A")、objc_getClass("A") 等归类为使用的类,@interface A : B 归类为 定义的类,然后计算差集。
基于源码扫描 有个已经实现的工具 -- fui[18],它的实现原理是查找所有 #import "A" 和所有的文件进行比对。
这里的多个可执行文件一般是指 APP 宿主程序与 Extension 程序,如果 APP 宿主程序与 Extension 程序都依赖同一个静态库库时,就会导致两个可执行文件中都包含相同的代码;个人觉得有两种解决方案:
结论:根据项目实际情况选用解决方案。
使用 resource_bundles 配合 xcassets 的方式来集成各个插件中的资源文件,因为 resource_bundle 中的资源在构建期能经过 Xcode 的优化,而 resource 中的资源则不能。并且这种形式可以将每个 pod 的资源放在自己的 Bundle 中,更方便管理。
结论:自定义 Pod 如果含有资源,尽量使用 resource_bundle 的方式引用资源。
结论:这一部分需要从提升个人编码素质、团队文化以及团队管理等方面入手。
还有一些优化方式,如二进制段压缩,__TEXT 段迁移等方式,大家可以去寻找相关的资料查看,这里只简单介绍相关的原理。
本文主要归纳总结了一些常用的瘦身方法,当然不同的项目需求以及业务场景都会产生一些对应的瘦身方法,大家可以根据自己的业务特性去寻找一些更好更优的瘦身技巧。
最后,祝大家周末愉快!
Let's be CoderStar!
相关链接
[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