如何从资源和代码层面实现App全方位瘦身?
作者国孩,原文发表于掘金,点击阅读原文查看作者更多文章
https://juejin.cn/post/6918686834906284040
引言
其实关于此类的博客网上有很多,可能有很多是原创,可能有很多是转载,当然也有很多是抄袭,前些天看到头条也有一篇,写的很好,但是觉得对于初学者,实质性意义觉得不大【因为毕竟要代码保密,资源保密】。
本人写此篇博客是因为觉得App已经迭代了接近20个版本,包体积至今没有优化过,于是本人就想着去优化ipa大小【完全使用本人项目开刀】。如果也想跟着尝试,那就Follow,共同优化包体积大小,从下载那一刻优化吧!!!
经过实际验证,本人负责项目从开始89MB,到达优化之后的75MB【项目名称不说啦,但是目的达到了,嘿嘿】,还是提高了不少滴。
共同提高,共同进步永远是我写博客的目的~共勉,欢迎点赞,给个小红心❤️
背景与问题
App Store规定了安装包大小超过200MB的App不能使用OTA【over-the-air】环境下载,也就是只能在WiFi环境下下载。所以200MB就成了App的生死线,一旦超过这条线就很可能会失去大量用户。【如果你的App要兼容iOS7和iOS8的话,苹果官方主二进制text段的大小不能超过60MB。如果没有达到这个标准,你甚至都没法提交App Store】
另外,App包体积过大,对用户更新升级率也会有很大影响。
综上所述,App 包过大既损害用户体验,影响升级率,还会导致无法提交 App Store 的情况和非 WiFi 环境无法下载这样可能影响到 App 生死的问题。那么,怎样对包大小进行瘦身和控制包大小的不合理增长就成了重中之重。
分析与定位
对于一个App 从0-1 以及后续团队不断的迭代,研发人员的不断增多,伴随着也有能力的参差不齐,代码质量的高低不同。如果一味的增加需求,一味的写冗余重复的代码,势必很难维护以及打出的ipa包体积越来愈大。经历过一些版本的迭代,会发现ipa包体积变大了很多,想到了这些,我们应该可以做些什么,或者苹果官方为我们做些什么呢?
官方App Thinning做了些啥呀?【从本质了解“官方瘦身”】
如果删除无用图片资源,是否能达到包体积大小优化呢?
或者压缩图片视频大小等,包体积可以减小嘛?
给代码瘦身,找出无用代码,复用代码等
其他一些方法
下面就按照上面一步一步跟着,给自己的App包瘦身。
官方App Thinning
App Thinning可以译成“应用瘦身”。指的是App store 和操作系统在安装iOS或者watchOS的 app 的时候通过一些列的优化,尽可能减少安装包的大小,使得 app 以最小的合适的大小被安装到你的设备上。
App Thinning 是由苹果公司推出的一项可以改善 App 下载进程的新技术,主要是为了解决用户下载 App 耗费过高流量的问题,同时还可以节省用户 iOS 设备的存储空间。现在的 iOS 设备屏幕尺寸、分辨率越来越多样化,这样也就需要更多资源来匹配不同的尺寸和分辨率。同时,App 也会有 32 位、64 位不同芯片架构的优化版本。如果这些都在一个包里,那么用户下载包的大小势必就会变大。
官方App Thinning包括了三个过程:slicing,bitcode,和 on-demand resources
3.1 Slicing
App Slicing在节省应用所需资源中发挥着最重要的作用。
App Thinning 会专门针对不同的设备来选择只适用于当前设备的内容以供下载。比如,iPhone 6 只会下载 2x 分辨率的图片资源,iPhone 6plus 则只会下载 3x 分辨率的图片资源。
Slicing 的主要的工作流程图和过程如下:
在Xcode中,选择好目标设备并且使用asset catalog提供多分辨率的图片资源,只有使用asset catalog才能正确使用Slicing作用于资源文件
在模拟器或者设备上编译并运行app
Xcode会自动构建针对你运行设备的“简化版app”,同时也是为了减少编译时间和进行本地的测试
打包app【为了及时发现不同设备的配置错误,可以在本地为目标设备导出“简化版app”,测试无误后再打包】
上传打包好的app到iTunes Connect。App store将会为上传的app归档创建不同的“简化版app”
在iTunes Connect中,发布一个预览版给合格的测试者进行测试
测试者通过TestFlight下载预览版。TestFlight会自动根据测试者的设备下载合适的“简化版app”
3.2 bitcode
Bitcode是一个编译好的程序的中间表示形式。上传到 iTunes Connect 中的包含Bitcode的app 将会在 App store 中进行链接和编译。苹果会对包含Bitcode的二进制app进行二次优化,而不需要提交一个新的app版本到app store中
3.3 On-Demand Resources
ODR(on-demand resources 随需应变资源)是iOS减少应用资源消耗的另外一种方法。比如多级游戏,用户需要的通常都是他们当前的级数以及下一级。ODR意味着用户可以下载他们需要的几级游戏。随着你的级数不断增加,应用再下载其他级数,并将用户成功过关的级数删掉。
当用户点击应用内容的时候,就会动态从App Store上进行下载,也就是说用户只会在需要的时候占用存储空间。这项功能有趣之处还在于当将这些内容在后台进行下载之后,当存储空间紧张的时候会自动进行删除。
如果要进一步减小 App 包体积的话,还需要在图片和代码上继续做优化。为了减小 App 安装包的体积,我们还能在图片上做些什么?
删除无用图片
删除无用图片应该是图片资源的优化空间最先想到的。那么,我们是如何找到并删除这些无用图片资源的呢?
对于删除图片,本人在项目中的实践采取的是下面的第二种第三种方法,大家跟着操作:
删除无用图片的思路
一个一个图片查找删除【工作量太大】
使用开源组件FengNiao
LSUnusedResources【最好用】
下面我们按照这个步骤开始着手去做~
4.1 方案思路
删除无用图片的过程可以分为总共6个步骤和图解【思路如下】
通过find命令获取App安装包中所有的资源文件,比如
find /Users/zhang**/Project/ -name
设置用到的资源的类型,比如jpg、gif、png、webp
使用正则匹配在源代码中找到使用到的资源名称,比如
pattern = @"@"(.+?)""
。使用find 命令找到所有资源文件,再去掉代码中使用到的资源文件,剩下的就是无用资源
对比按照规则设置的资源名,需要在匹配使用资源的正则表达式里添加相应的规则,比如@“image_%d”
确认无用资源后,就可以对这些无用资源执行删除操作了。这个删除操作,你可以使用 NSFileManger 系统类提供的功能来完成。
4.2 费时有效操作
可以找到Assets.xcassets,对照着里面的一张张图片,然后在工程目录搜索,如果工程没有使用到的话,那就删除吧,此方法很土但费时,有没有好的操作方法呢?
4.3 开源组件FengNiao
FengNiao–一个删除Xcode工程中无用图片资源的神器!FengNiao一个命令行工具,由iOS界的大神onevcat喵神使用swift开发并开源。
4.3.1 安装FengNiao
下载FengNiao
git clone https://github.com/onevcat/FengNiao.git
进入到FengNiao目录下
执行./install.sh
fengniao会自动编译安装到/usr/local/bin目录下,然后成功结果如下:
4.3.2 使用 FengNiao
cd到项目工程中,直接使用fengniao命令就可以【提供list(列表查看)、delete(直接删除)、ignore(忽略这些文件)三个命令】
通过输入l,就可以列举未使用的图片,大家对着看就好了,嘿嘿嘿,体验还不错!!!
4.4 LSUnusedResources
本人建议这个是最好用的。大家可以查看LSUnusedResources源码
如果你不想自己重新写一个工具的话,可以选择开源的工具直接使用。本人觉得目前最好用的是 LSUnusedResources【运行此代码,最好的是还有人在维护,给作者一个赞】特别是对于使用编号规则的图片来说,可以通过直接添加规则来处理。
使用方式也很简单,可以参看下面的动画演示:【运行代码之后的界面】
4.4.1 下载代码,并运行出来的结果如下
4.4.2 将根目录输入进去,选择匹配法则,就可以可视化无用的图片
经过上面的三个方法,可以将无用图片彻底删除了,嘿嘿,完成优化了3-5MB的大小。不错不错~
图片视频等压缩
无用图片资源处理完了,那么有用的图片还有瘦身的空间吗?答案是有的。
5.1 视频压缩方面
对于本人项目,安装App时,会有几个引导介绍视频,原本每一个视频大约都是2-3MB,但是对于包的减少,本人又让UI和视频组对4个引导视频压缩,最后大约每个引导都是1.3MB以内
此时引导视频优化了4-5MB优化空间,下面进行图片压缩方案探究。
5.2 图片资源压缩
对于 App 来说,图片资源总会在安装包里占个大头儿。对它们最好的处理,就是在不损失图片质量的前提下尽可能地做压缩。
5.2.1 WebP
目前比较好的压缩方案是,将图片转成 WebP。WebP 是 Google 公司的一个开源项目。
WebP 压缩率高,而且肉眼看不出差异,同时支持有损和无损两种压缩模式。比如,将 Gif 图转为 Animated WebP ,有损压缩模式下可减少 64% 大小,无损压缩模式下可减少 19% 大小。
WebP 支持 Alpha 透明和 24-bit 颜色数,不会像 PNG8 那样因为色彩不够而出现毛边。
怎么把图片转成WebP?
Google 公司在开源 WebP 的同时,还提供了一个图片压缩工具 cwebp来将其他图片转成 WebP。cwebp 使用起来也很简单,只要根据图片情况设置好参数就行。
cwebp 语法如下:
cwebp [options] input_file -o output_file.webp
比如,你要选择无损压缩模式的话,可以使用如下所示的命令:
cwebp -lossless original.png -o new.webp
其中,-lossless 表示的是,要对输入的 png 图像进行无损编码,转成 WebP 图片。不使用 -lossless ,则表示有损压缩。
5.2.2 腾讯开发的iSpart
iSpart 是一个 GUI 工具,操作方便快捷,可以实现 PNG 格式转 WebP,同时提供批量处理和记录操作配置的功能。如果是其他格式的图片要转成 WebP 格式的话,需要先将其转成 PNG 格式,再转成 WebP 格式。它的GUI界面如下:
不过,WebP 在 CPU 消耗和解码时间上会比 PNG 高两倍。所以,我们有时候还需要在性能和体积上做取舍。
本人建议如果图片大小超过了 100KB,你可以考虑使用 WebP;而小于 100KB 时,你可以使用网页工具 TinyPng或者 GUI 工具ImageOptim进行图片压缩。这两个工具的压缩率没有 WebP 那么高,不会改变图片压缩方式,所以解析时对性能损耗也不会增加。
经过对部分图片的压缩,少了大约2-4MB的优化空间,到目前为止差不多有10-12MB的优化空间。嘿嘿~
代码瘦身
App 的安装包主要是由资源和可执行文件组成的,所以在掌握了对图片资源的处理方式后,需要再一起来看看对可执行文件的瘦身方法
6.1 通过Appcode找出无用代码
如果工程量不是很大的话,我还是建议你直接使用 AppCode 来做分析【本人项目就是使用它】当代码量过百万行时 AppCode 的静态分析会“歇菜”。毕竟代码量达到百万行的工程并不多。而,那些代码量达到百万行的团队,则会自己通过 Clang 静态分析来开发工具,去检查无用的方法和类。
用 AppCode 做分析的方法很简单,直接在 AppCode 里选择 Code->Inspect Code 就可以进行静态分析。
静态分析完以后,我们可以在 Unused code 里看到所有的无用代码,如下:【感觉棒棒哒💯】
接下来,说一下这些无用代码的主要类型。
无用类:Unused class 是无用类,Unused import statement 是无用类引入声明,Unused property 是无用的属性;
无用方法:Unused method 是无用的方法,Unused parameter 是无用参数,Unused instance variable 是无用的实例变量,Unused local variable 是无用的局部变量,Unused value 是无用的值;
无用宏:Unused macro 是无用的宏。
无用全局:Unused global declaration 是无用全局声明。
看似 AppCode 已经把所有工作都完成了,其实不然。下面,列举下 AppCode 静态检查的问题:
JSONModel 里定义了未使用的协议会被判定为无用协议;
如果子类使用了父类的方法,父类的这个方法不会被认为使用了;
通过点的方式使用属性,该属性会被认为没有使用;
使用 performSelector 方式调用的方法也检查不出来,比如
self performSelector:@selector(arrivalRefreshTime)
;运行时声明类的情况检查不出来。比如通过 NSClassFromString 方式调用的类会被查出为没有使用的类,比如
layerClass = NSClassFromString(@“SMFloatLayer”)
。还有以[[self class] accessToken]
这样不指定类名的方式使用的类,会被认为该类没有被使用。像 UITableView 的自定义的 Cell 使用 registerClass,这样的情况也会认为这个 Cell 没有被使用。
基于以上种种原因,使用 AppCode 检查出来的无用代码,还需要人工二次确认才能够安全删除掉。
经过Appcode工具,大约找出优化了2-4MB的无用的类和代码。
除了这种方式,还有没有其他方法了呢?答案是有的,但是下面方法本人没有用到项目中尝试,但是还是要和大家说下,大家可以去尝试。
6.2 LinkMap结合Mach-O找出无用代码
可执行文件的瘦身方法。可执行文件就是 Mach-O 文件,其大小是由代码量决定的。通常情况下,对可执行文件进行瘦身,就是找到并删除无用代码的过程。
查找无用代码,按照找无用图片的思路,即:
首先,找出方法和类的全集;
然后,找到使用过的方法和类;
接下来,取二者的差集得到无用代码;
最后,由人工确认无用代码可删除后,进行删除即可。
接下来,我们就看看具体的代码瘦身方法吧。
6.2.1 Link Map File配置
点击工程,选择 Build Setting 选项,搜索 map ,可以看到如下界面。将 Write Link Map File 设置为 Yes 后,Build结束后,会在默认路径下生成一个 Link Map File 文件,该文件是 txt 格式的。点击 Path to Link Map File ,可以设置 Debug 或 Release 模式下的生成路径。设置选项如下图所示:
查到Path to Map File路径【本人采用的模拟器】
打开txt文件
6.2.2 Link Map讲解
LinkMap 文件分为三部分:Object File、Section 和 Symbols
其中:
Object File 包含了代码工程的所有文件;
Section 描述了代码段在生成的 Mach-O 里的偏移位置和大小;
Symbols 会列出每个方法、类、block,以及它们的大小。
得到了代码的全集信息以后,我们还需要找到已使用的方法和类,这样才能获取到差集,找出无用代码。所以接下来,说说怎么通过 Mach-O 取到使用过的方法和类。
objc_selrefs 里的方法一定是被调用了的。objc_classrefs 里是被调用过的类,__objc_superrefs 是调用过 super 的类。通过 __objc_classrefs 和 __objc_superrefs,我们就可以找出使用过的类和子类。
6.2.3 用 MachOView查看可执行文件验证上面
如图上所示,可以看到 __objc_selrefs
、__objc_classrefs
和、__objc_superrefs
等这三个 section。
注意:
但是,这种查看方法并不是完美的,还会有些问题。原因在于, Objective-C 是门动态语言,方法调用可以写成在运行时动态调用,这样就无法收集全所有调用的方法和类。所以,通过这种方法找出的无用方法和类就只能作为参考,还需要二次确认。
其他瘦身
Clang/LLVM 编译器优化选项
都知道 Xcode 是使用 Clang 来编译 Objective-C 语言的,Clang 的优化选项在其文档 clang - Code Generation Options 中可以查阅得到。DE-Xcode 只提供给我们 6 个等级的编译选项,在 Xcode -> Build Setting -> Apple LLVM 9.0 - Code Generation -> Optimization Level 中进行设置
None[-O0]
: 编译器不会优化代码,意味着更快的编译速度和更多的调试信息,默认在 Debug 模式下开启。Fast[-O,O1]
: 编译器会优化代码性能并且最小限度影响编译时间,此选项在编译时会占用更多的内存。Faster[-O2]
:编译器会开启不依赖空间/时间折衷所有优化选项。在此,编译器不会展开循环或者函数内联。此选项会增加编译时间并且提高代码执行效率。Fastest[-O3]
:编译器会开启所有的优化选项来提升代码执行效率。此模式编译器会执行函数内联使得生成的可执行文件会变得更大。一般不推荐使用此模式。Fastest Smallest[-Os]
:编译器会开启除了会明显增加包大小以外的所有优化选项。默认在 Release 模式下开启。Fastest, Aggressive Optimization[-Ofast]
:启动 -O3 中的所有优化,可能会开启一些违反语言标准的一些优化选项。一般不推荐使用此模式。
Fastest Smallest[-Os]
极小限度会影响到包大小,而且也保证了代码的执行效率,是最佳的发布选项,一般 Xcode 会在 Release 下默认选择 Fastest Smallest[-Os]
选项,较老的项目可能没有自动勾选。
Swift Complier/LLVM 编译优化选项
Swift 语言的编译器是 swiftlang,同时也是基于 LLVM 后端的。Xcode 9.3 版本之后 Swift 编译器会提供新的选项来帮助减少 Swift 可执行文件的大小:
部门协商与程序员编程素质【其他Tips】
不要随便依赖第三方类库,可能仅仅用到1个库中的几个方法,就引入巨形库,删除不使用的三方库。功能用的少但是体积大的三方库可以考虑自己重写。合并功能重复的三方库。
引导产品、交互、设计,使用同一种方式开发,以复用代码
少用多行宏,多用函数封装
代码复用,禁止拷贝代码,共用代码下沉为底层组件
尽量将图片资源放入 Images.xcassets 中,包括 pod 库的图片。Images.xcassets 中的图片加载后会有缓存,提升加载速度,并且在最终打包时会自动进行压缩,再根据最终运行设备进行 2x 和 3x 分发。
对于一些非必要的大资源文件,例如字体库、换肤资源,可以在 App 启动后通过异步下载到本地,而不用直接放在 ipa 包内。
当然还有一个终极大招砍需求,哈哈哈【此种方法可能不太容易实现】
总结
今天这篇文章,主要分享的是 App 安装包的一些瘦身方案。可以把包瘦身方案根据 App 的代码量等因素,划分为两种。
对于上线时间不长的新 App 和那些代码量不大的 App 来说,做些资源上的优化,再结合使用 AppCode 就能够有很好的收益。而且把这些流程加入工作流后,日常工作量也不会太大。【本人目前项目就是这种】
但是,对于代码量大,而且业务需求迭代时间很长的 App 来说,包大小的瘦身之路依然任道重远,这个领域的研究还有待继续完善。LinkMap 加 Mach-O 取差集的结果也只能作为参考,每次人工确认的成本是非常大的,只适合突击和应急清理时使用。最后日常采用的方案,可能还是用运行时检查类的方式,这种大粒度检查的方式精度虽然不高,但是人工工作量会小很多。
大致情况就是这样,动手吧!,欢迎点赞博客及关注本人,后期会继续分享更多的干货供大家分析参考点评!!!