iOS 包大小优化 - 文章教程

iOS 包大小优化

发布于 2021-02-24 字数 5689 浏览 1357 评论 0

官方 App Thinning

iOS9 开始支持。专门针对不同的设备来选择只适用于当前设备的内容以供下载。比如 2x、3x 图片,下载适合自己设备的芯片指令集架构文件。这项工作的大部分有 Xcode 和 App Store 来完成

ming$ du -h Reveal.framework/*
  0B  Reveal.framework/Headers
  0B  Reveal.framework/Reveal
 16K  Reveal.framework/Versions/A/Headers
 21M  Reveal.framework/Versions/A
 21M  Reveal.framework/Versions
 
 ming$ file Reveal.framework/Versions/A/Reveal 
Reveal.framework/Versions/A/Reveal: Mach-O universal binary with 5 architectures: [i386:current ar archive] [arm64]
Reveal.framework/Versions/A/Reveal (for architecture i386): current ar archive
Reveal.framework/Versions/A/Reveal (for architecture armv7):  current ar archive
Reveal.framework/Versions/A/Reveal (for architecture armv7s): current ar archive
Reveal.framework/Versions/A/Reveal (for architecture x86_64): current ar archive
Reveal.framework/Versions/A/Reveal (for architecture arm64):  current ar archive

有三方方式:

  1. App Slicing,会在提交审核后,对 App 做切割,创建不同的变体,这样就可以使用到不同的设备。
  2. On Demand Resources:按需加载资源是在 App 第一次安装后可下载的资源,多用在游戏根据用户的关卡进度下载资源,过关的资源会被删掉,减小初装 App 包的大小。需要在 Xcode 中设置 Enable On Demand Resources 为是。
  3. Bitcode:是以编译好的程序的中间表示形式,成为字节码。苹果会对它进行优化,同时有新的架构出来不需要开发者上传新包,可以用字节码直接生成新的架构包。但是由于上传的是中间形态字节码,本地的DSYM无效,需要从App Store 下载对应的DSYM,不然集成的崩溃收集工具无法解析。

无用图片资源

分为 6 步:

  1. 通过 find 命令获取 App 安装包中的所有资源文件,比如 find /Users/daiming/Project/ -name。
  2. 设置用到的资源的类型,比如 jpg、gif、png、webp。
  3. 使用正则匹配在源码中找出使用到的资源名,比如 pattern = @”@”(.+?)””。
  4. 使用 find 命令找到的所有资源文件,再去掉代码中使用到的资源文件,剩下的就是无用资源了。
  5. 对于按照规则设置的资源名,我们需要在匹配使用资源的正则表达式里添加相应的规则,比如 @“image_%d”。
  6. 确认无用资源后,就可以对这些无用资源执行删除操作了。这个删除操作,你可以使用 NSFileManger 系统类提供的功能来完成。

代表性的开源工具

图片资源压缩

WebP

WebP 压缩率高,而且肉眼看不出差异,同时支持有损和无损两种压缩模式。比如,将 Gif 图转为 Animated WebP ,有损压缩模式下可减少 64% 大小,无损压缩模式下可减少 19% 大小。

WebP 支持 Alpha 透明和 24-bit 颜色数,不会像 PNG8 那样因为色彩不够而出现毛边。

WebP压缩工具

cwebp [options] input_file -o output_file.webp
// -lossless 无损压缩
cwebp -lossless original.png -o new.webp

GUI工具 可以实现 PNG 格式转 WebP,同时提供批量处理和记录操作配置的功能

WebP在 CPU 消耗和解码时间上回比 PNG 高两倍,图片大于100kb时可以考虑。

网页工具 tinypngGUI工具imageoptim进行图片压缩,压缩率没有WebP那么高,不会改变图片压缩方式。

代码瘦身

首先,找出方法和类的全集;

然后,找到使用过的方法和类;

接下来,取二者的差集得到无用代码;

最后,由人工确认无用代码可删除后,进行删除即可。

LinkMap 结合 Mach-O 找无用代码

我们可以通过分析 LinkMap 来获得所有的代码类和方法的信息。获取 LinkMap 可以通过将 Build Setting 里的 Write Link MapFile 设置为 Yes,然后指定 Path to Link Map File 的路径就可以得到每次编译后的 LinkMap 文件了。

LinkMap 文件分为三部分:Object File、Section 和 Symbols。

  • Object File 包含了代码工程的所有文件;
  • Section 描述了代码段在生成的 Mach-O 里的偏移位置和大小;
  • Symbols 会列出每个方法、类、block,以及它们的大小。

iOS 的方法都会通过 objc_msgSend 来调用。而,objc_msgSend 在 Mach-O 文件里是通过__objc_selrefs 这个 section 来获取 selector 这个参数的。

所以,__objc_selrefs 里的方法一定是被调用了的。__objc_classrefs 里是被调用过的类,__objc_superrefs 是调用过 super 的类。通过 __objc_classrefs__objc_superrefs,我们就可以找出使用过的类和子类。

通过 AppCode 找出无用代码

运行时检查类是否真正被使用过

通过 ObjC 的 runtime 源码,我们可以找到怎么判断一个类是否初始化过的函数,如下:

#define RW_INITIALIZED (1<<29)
bool isInitialized() {
   return getMeta()->data()->flags & RW_INITIALIZED;
}

isInitialized 的结果会保存到元类的 class_rw_t 结构体的 flags 信息里,flags 的 1<<29 位记录的就是这个类是否初始化了的信息。而 flags 的其他位记录的信息,你可以参看bjc 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)

flags 采用位方式记录布尔值的方式,易于扩展、所用存储空间小、检索性能也好。所以,经常阅读优秀代码,特别有助于提高我们自己的代码质量。

如果你对这篇文章有疑问,欢迎到本站 社区 发帖提问或使用手Q扫描下方二维码加群参与讨论,获取更多帮助。

扫码加入群聊

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

目前还没有任何评论,快来抢沙发吧!

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

2583 文章
29 评论
84935 人气
更多

推荐作者

清风夜微凉

文章 1 评论 0

为你鎻心

文章 2 评论 0

xxhui

文章 0 评论 0

1PKOH46yx8j0x

文章 0 评论 0

Arthur

文章 0 评论 0