原因

  • 苹果对非wifi( OTA:over the air)环境下载的包大小有限制
  • 包过大会影响升级体验,降低升级率

瘦身方式

App Thinning

原理

  • 图片:针对不同的设备选择只适用于当前设备的内容,根据屏幕倍数选择 2x 或 3x 的图片。

  • 代码:不同的设备只会选择下载其芯片指令集对应架构的文件。

    在 App Thinning 之前,系统会同时生成 i386x86_64arm64armv7armv7s 等多个芯片指令集架构;客户机会将所有文件都下载到本地。

方式

  • 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 字段里记录了什么?