一、前言
大于150M的App只能通过WiFi来下载的,假如正巧有用户想使用你们的App,但没有WiFi,又看了看其他竞品,而他们的App正好小于150M,那这个用户你铁定流失掉了,所以在提升新用户这方面,App瘦身也起到了一定的作用。下面就谈一谈可以用到的瘦身方法。
二、App Thinning
1.介绍
App Thinning是苹果推出的一项可以改善App下载进程的新技术,主要就是为了解决用户下载App耗费过高流量的问题,同时还可以节省用户iOS设备的存储控件。App Thinning会专门针对同的设备来选择只适用于当前设备的内容以供下载。比如,2x和3x,类似plus的机型只会下载3x分辨率的图片,而其它机型只下载2x。
我们所知道的芯片指令集架构文件,有如下几种:
模拟器:
x86_64
、i386
真机:arm64
、armv7
、armv7s
App Thinning会在下载时选择最适合自己的设备的芯片指令集架构文件。
App Thining有三种方式:App Slicing
、Bitcode
、On-Demand Resources
- App Slicing:在上传iTunes Connect后,对App做切割,创建不同的变体,这样就可以使用到不同的设备
- On-Demand Resources:主要是为游戏多关卡场景服务。它会根据关卡进度下载随后几个关卡资源,并且已经过关的资源会被删掉,这样就可以减少出装App的包大小。
- Bitcode:针对特定设备进行包大小优化,优化不明显
2.使用
在创建工程后会看到Assets.xcassets
文件夹,这里就是放所有图片资源的地方,我们只需要将2x和3x图放入进去,剩下的xcode会自动帮我们完成。
三、定时清理无用的图片资源和类文件
随着新需求开发的迭代速度越来越快,很多图片资源和类文件也会越来越多,如果不定时清理,一来会增加包的大小,二来在开发时众多的类也让人看起来眼花缭乱,在这里给大家介绍两个开源的Mac应用程序,LSUnusedResources和XcodeProjectArrangementTool,对于LSUnusedResources
来说,灵活性更高些,可以自定义添加正则来筛选,不过在使用这两种方法时,需要进行二次确认。
而它们主要的原理可以分为如下6个步骤:
- 通过 find 命令获取 App 安装包中的所有资源文件,比如
find xxx/Project/ -name
- 设置用到的资源的类型,如jpg、png、gif、webP
- 使用正则匹配在源码中找出使用到的资源名,比如
pattern = @"@"(.+?)""
。- 使用find命令找到的所有资源文件,再去掉代码中使用到的资源文件,剩下的就是无用的资源了
- 对于按照规则设置的资源名,我们需要在匹配使用资源的正则表达式里添加相应的规则,比如
image_%d
- 确认无用资源后,对这些无用的资源进行删除操作。
四、图片压缩
一个华丽丽的App里成有着数不尽的资源图片,那对App进行瘦身操作,这些图片的处理也就成为了一个重点的操作对象。而对它们最好处理,就是在不损失图片质量的前提下尽可能地压缩处理。目前比较好的压缩方案是,将图片转成WebP。WebP是Google的一个开源项目,使用它的好处是:
- WebP压缩率高,而且肉眼看不出差异,同时支持有损和无损两种压缩模式。比如将Gif图转为
Animated WebP
,有损压缩模式下可减少64%大小,无损压缩模式下可减少19%大小。- WebP支持Alpha透明和24-bit颜色数,不会像PNG8好样因为色彩不够而出现毛边。
那如何将图片转为WebP呢?
1.cwebp
Google在开源WebP的同时,还提供了一个图片压缩工具,cwebp,使用语法如下:
cwebp [options] input_file -o output_file.webp
比如,无损压缩模式
cwebp -lossless a.png -o b.webp
-lossless
表示进行无损编码,不使用-lossless
则表示有损压缩
-q (float)
参数,可以在不损失图片质量的情况下进行最大化的压缩
- 小于256色适合无损压缩,压缩率高。
-lossless -q 100
- 大于256色使用75%有损压缩。
-q 75
- 远大于256色使用75%以下压缩率。
-q 50 -m 6
2.iSparta
iSparta是由腾讯开发的GUI工具,操作方便快捷,除了实现将PNG转WebP外,还提供批量处理和记录操作配置的功能。
如果是其他格式的图片要转成WebP的话,需要先将其转成PNG格式,再转成WebP格式
要使用WebP格式的图片,我们需要在显示图片时使用libwebp
进行解析,libwebp Demo。
WebP在CPU消耗和解码时间会比PNG高两倍,所以在使用时,需要在性能和包瘦身上做取舍。
建议是,图片大小超过100KB,可以考虑使用WebP,小于100KB,可以使用网页工具TinyPng或者GUI工具ImageOptim进行图片压缩。这两个工具压缩率没有WebP高,不会改变图片压缩方式,所以解析时对性能损耗也不会增加。
3.矢量图PDF
在Xcode6以上就支持在Asset Catalog
上使用PDF
矢量图来代替原来不同的分辨率图片。只需要将矢量图导入Asset Catalog
中,然后在Show Attributes inspector
设置Scale Factor
为Single Vector
即可。这样就不需要考虑2x与3x,工程里也就只有一套资源图,而剩下的工作还是全都交给Xcode即可。
五、代码瘦身
- 首先,找出方法和类的全集
- 然后,找到使用过的方法和类
- 接下来,取两者的差集得到无用的代码
- 最后,由人工确认无用代码可删除后,进行删除
1.LinkMap结合Mach-O找无用代码
打开LinkMap
设置
TARGETS
->Build Settings
->Write Link Map File
设置为YES
- 设置
LinkMap
文件路径,添加Path to Link Map File
输出路径
LinkMap
文件分三部分:Object File
、Section
、Symbols
Objct File
包含了代码工程的所有文件Section
描述了代码段在生成的Mach-O里的便宜位置和大小Symbols
会列出每个方法、类、block以及它们的大小
通过LinkMap
,不光可以统计出所有的方法和类,还能够清晰地看到代码所占包大小的具体分布,进而有针对性地进行代码优化。
得到了代码的全集信息以后,我们还需要找到已使用的方法和类,这样才能获取到差集,找出无用代码。下面就说说怎样通过Mach-O
取到使用过的方法和类。
iOS的方法都会通过objc_msgSend
来调用,而objc_msgSend
在Mach-O
文件里是通过__objc_selrefs
这个section
来获取selector
这个参数的。所以__objc_selrefs
里的方法一定是被调用了的。__objc_classrefs
里是被调用过的类,__objc_superrefs
是调用过supper
的类。通过__objc_classrefs
和__objc_superrefs
,我们就可以找出使用过的类和子类。
然后可以使用MachOView来查看Mach-O
文件里的信息。
将编译后生成的xxx.app包揭开,取出对应的工程文件同名的文件,然后使用MachOView打开,就可以看到里面的信息了
但这种方法并不是完美的,还会有些问题。原因就在于,Objective-C是一门动态语言,方法调用可以写成在运行时动态调用,这样就无法收集全所有调用的方法和类。所以通过这种方法找出的无用方法和类就只能作为参考,还需要二次确认。
2.通过AppCode找出无用代码
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静态检查的问题:
- JSONModel里定义了未使用的协议会被判定为无用协议
- 如果子类使用了父类的方法,父类的这个方法不会被认为使用了
- 通过点的方式使用属性,该属性会被认为没有使用
- 使用
performSelector
方式调用的犯法也检查不出来- 运行时声明类的情况检查不出来。比如
NSClassFromString
方式调用的类会被查出为没有使用的类,像这样不指定类名的方式使用的类,会被认为该类没有被使用。
基于以上种种原因,使用AppCode检查出来的无用代码,也需要人工二次确认后,才能够安全删除掉。
3.运行时检查类是否真正被使用过
在App不断迭代过程中,新人不断接受、为务功能需求不断替换,会留下很多无用代码。这些代码在执行静态检查时会被用到,但是使用它的入口可能已经没有了,所以这部份代码也是可以被删掉的。
通过runtime
源码,我们可以找到如何判断一个类是否初始化过的函数
#define RW_INITIALIZED (1<<29)
bool isInitialized() {
return getMeta()->data()->flags & RW_INITIALIZED;
}
isInitialized
的结果会保存到猿类class_rw_t
结构体的flags
信息里,flags
的1<<29位记录的就是这个类是否初始化了的信息。而flags
的其它位记录的信息,可以参看源码
// 类的方法列表已修复
#define RW_METHODIZED (1<<30)
// 类已经初始化了
#define RW_INITIALIZED (1<<29)
// 类在初始化过程中
#define RW_INITIALIZING (1<<28)
// class_rw_t->ro 是 class_ro_t 的堆副本
#define RW_COPIED_RO (1<<27)
// 类分配了内存,但没有注册
#define RW_CONSTRUCTING (1<<26)
// 类分配了内存也注册了
#define RW_CONSTRUCTED (1<<25)
// GC:class 有不安全的 finalize 方法
#define RW_FINALIZE_ON_MAIN_THREAD (1<<24)
// 类的 +load 被调用了
#define RW_LOADED (1<<23)
既然能过在运行中看到类是否初始化了,那么我们就能够找出有哪些类是没有初始化的,即找到在真实环境中没有用到的类,进行二次确认后,再清理掉。
网友评论