包大小控制学习笔记
原因
- 苹果对非wifi( OTA:over the air)环境下载的包大小有限制
- 包过大会影响升级体验,降低升级率
瘦身方式
App Thinning
原理
-
图片:针对不同的设备选择只适用于当前设备的内容,根据屏幕倍数选择 2x 或 3x 的图片。
-
代码:不同的设备只会选择下载其芯片指令集对应架构的文件。
在 App Thinning 之前,系统会同时生成
i386
、x86_64
、arm64
、armv7
、armv7s
等多个芯片指令集架构;客户机会将所有文件都下载到本地。
方式
- App Slicing:app 被上传到 appstore connect 之后,苹果后台系统会对 app 做切割,创建不同的版本
- On-Demand Resources:针对游戏多关卡场景,已经通过的关卡的资源会被删掉
- BitCode:针对特定设备进行包大小优化
如何使用
创建 Asset Catalog,将图片放进生成的 xcassets 文件中即可。
删除无效图片
- 命令行:
find <#path#> -name
,设置资源类型:png/jpg/gif/webp
,正则匹配资源名:pattern = @"@"(.+?)""
- LSUnusedResources
图片压缩
使用 WebP 格式。
策略:如果图片大小超过了 100KB,你可以考虑使用 WebP;而小于 100KB 时,你可以使用网页工具 TinyPng 或者 GUI 工具 ImageOptim 进行图片压缩。
优点:
- 压缩率高:gif 转成 webp,有损方式可减少 64%,无损可减少 19% 大小
- 支持 Alpha 透明 24-bit 颜色数,不会向 png8 那样因为色彩不够出现毛边
缺点:
在 CPU 消耗和解码时间上会比 PNG 格式高两倍
cwebp
cwebp [options] input_file -o output_file.webp
几个重要的参数说明:
-lossless
无损。使用示例:
cwebp -lossless original.png -o new.webp
-q float
压缩率。
- 小于 256 色适合无损压缩,压缩率高,参数使用 -lossless -q 100;
- 大于 256 色使用 75% 有损压缩,参数使用 -q 75;
- 远大于 256 色使用 75% 以下压缩率,参数 -q 50 -m 6。
iSparta
使用示例
代码瘦身
LinkMap 结合 Mach-O 找无用代码
- 设置 Build Setting 里的
Write Link Map File
=>YES
- 设置
Path to Link Map
LinkMap 文件分为三部分:Object File、Section 和 Symbols。
- Object File 包含了代码工程的所有文件;
- Section 描述了代码段在生成的 Mach-O 里的偏移位置和大小;
- Symbols 会列出每个方法、类、block,以及它们的大小。
将编译生成的 app 文件解包,取出执行文件,通过 MachOView
软件查看内容,查找 Mach-O 文件的 __objc_selrefs
、__objc_classrefs
和 __objc_superrefs
这种方式无法获取在运行时动态调用的情况
AppCode
- 静态分析
- 适合工程不算太大的情况,代码量百万行以上可能会卡死
- Code -> Inspect Code
可能存在的问题:
- JSONModel 里定义了未使用的协议会被判定为无用协议;
- 如果子类使用了父类的方法,父类的这个方法不会被认为使用了;
- 通过点的方式使用属性,该属性会被认为没有使用;
- 使用 performSelector 方式调用的方法也检查不出来,比如 self performSelector:@selector(arrivalRefreshTime);
- 运行时声明类的情况检查不出来。
通过 AppCode 找出来的无用代码,需要人工确认无误后再删除。
运行时检查
#define RW_INITIALIZED (1<<29)
bool isInitialized() {
return getMeta()->data()->flags & RW_INITIALIZED;
}
sInitialized 的结果会保存到元类的 class_rw_t 结构体的 flags 信息里,flags 的 1«29 位记录的就是这个类是否初始化了的信息。而 flags 的其他位记录的信息,你可以参看 objc runtime 的源码,如下:
// 类的方法列表已修复
#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)
问题
苹果公司为什么要设计元类:
- 结构体设计能力:元类和类的结构体非常类似,为什么不合在一起用一个结构体?
- 用过 runtime 接口开发没:元类和类创建的时机是不是一样的,为什么?
- 是否有深入探究的意识:元类的 flag 字段里记录了什么?