灵犀iOS包体积优化

前言

灵犀iOS 端从 2021年年初起就把性能优化作为今年的重点工作,为契合公司“狠抓质量”的战略目标,灵犀iOS团队将从启动速度,包体积,CPU占用,内存占用等几方面进行优化,3月初启动了包大小优化。
如今一个月过去了。灵犀在继续探索包大小优化时实践了更多思路,包括构建配置、图片压缩、__TEXT 段迁移、二进制段压缩等。这些优化项在业务入侵较少的前提下给灵犀带来了显著的包大小收益。同时,学习整个业界在包大小优化上的不同方案,结合我们自身做的事情写了这篇博客。

一、安装包的构成

当我们通过构建,获得了一个经过了 App Slicing 后的 ipa 文件后,将其用 zip 解压缩方式解压,进入 .app 文件后,我们就可以直观地看到安装包中的内容。


image.png

AppStore安装包,往往包含资源与 iOS 上的可执行文件 Mach-O 文件两部分,安装包瘦身也是从这两部分进行。

Mach-O 文件

Mach-O 文件是 iOS 上的可执行文件,它是由代码源文件经过编译和静态链接获得。经过 App Slicing 之后的 Mach-O 文件往往仅包含单个架构。使用 MachOView 等工具,我们可以直观了解 Mach-O 中包含的内容。


image.png

同时,Link Map 文件能更进一步帮助我们分析 Mach-O 文件的构成。
Link Map文件是Xcode产生可执行文件的同时生成的链接信息,用来描述可执行文件的构造成分,包括代码段(__TEXT)和数据段(__DATA)的分布情况。只要设置Project->Build Settings->Write Link Map File为YES,并设置Path to Link Map File,build完后就可以在设置的路径看到LinkMap文件了:


image.png
image.png

二、资源大小优化

资源瘦身主要是去掉无用资源和压缩资源,资源包括图片、音视频文件、配置文件以及多语言wording。无用资源是指资源在工程文件里,但没有被代码引用。检查方法是,用资源关键字(通常是文件名,图片资源需要去掉@2x @3x),搜索代码,搜不到就是没有被引用。当然,有些资源在使用过程中是拼接而成的(如loading_xxx.png),需要手工过滤。

2.1、使用 ImageOptim 压缩图片

资源压缩主要对png进行无损压缩,用的是ImageOptim工具。不建议对资源做有损压缩,有损压缩需要设计一个个检查,通常压缩后效果不尽人意。无损压缩也有一些不足,实践后发现,虽然放入 Asset Catalog 的图片大小有了明显减小,但是构建的产物的大小却几乎没有变化。原因是Xcode 中,构建 Asset Catalog 的工具 actool 会首先对 Asset Catalog 中的 png 图片进行解码,得到 Bitmap 数据,然后再运用 actool 的编码压缩算法进行编码压缩处理。无损压缩通过变换图片的编码压缩算法减少大小,但是不会改变 Bitmap 数据。对于 actool 来说,它接收的输入没有改变,所以无损压缩无法优化 Assets.car 的大小。但是好在灵犀工程中由于组件化的原因,我们使用的资源图片90%都是以bundle文件夹的形式导入工程中的,图片资源也不算很多,整体给我们带来了1.5M的包大小收益。
优化前后对比,几乎看不出差别:

image.png

2.2、文件压缩

除了占比最大的图片资源,安装包内可能还有不少文本文件资源,如 JSON 文件、HTML 文件等。这些文本文件的压缩也能带来包大小优化效果。目前灵犀项目内文本文件体积还不大,暂时不需要此步骤,随着工程越来越大,未来某一时间也是能用上的,文本文件压缩一搬可以用以下方案

文本文件压缩方案由三部分组成:

1、压缩阶段:在 Build Phase 中添加脚本,构建期间对白名单内的文本文件做 zip 压缩;

2、解压阶段:在 App 启动阶段,在异步线程中进行解压操作,将解压产物存放到沙盒中;

3、读取阶段:在 App 运行时,hook 读取这些文件的方法,将读取路径从 Bundle 改为沙盒中的对应路径;

这一方案能在业务入侵较少的前提下完成压缩优化。后续这一方案也可以进一步拓展,应用在更多类型的文件上。

三、Mach-O 文件优化

在资源优化的同时,Mach-O 文件的优化也必不可少。

3.1可执行文件瘦身

回到我们的可执行文件瘦身问题,LinkMap文件可以帮助我们寻找优化点。

1. 查找无用selector

以往C++在链接时,没有被用到的类和方法是不会编进可执行文件里。但Objctive-C不同,由于它的动态性,它可以通过类名和方法名获取这个类和方法进行调用,所以编译器会把项目里所有OC源文件编进可执行文件里,哪怕该类和方法没有被使用到。

结合LinkMap文件的__TEXT.__text,通过正则表达式([+|-][.+\s(.+)]),我们可以提取当前可执行文件里所有objc类方法和实例方法(SelectorsAll)。再使用otool命令otool -v -s __DATA __objc_selrefs逆向__DATA.__objc_selrefs段,提取可执行文件里引用到的方法名(UsedSelectorsAll),我们可以大致分析出SelectorsAll里哪些方法是没有被引用的(SelectorsAll-UsedSelectorsAll)。注意,系统API的Protocol可能被列入无用方法名单里,如UITableViewDelegate的方法,我们只需要对这些Protocol里的方法加入白名单过滤即可。

另外第三方库的无用selector也可以这样扫出来的。

2. 查找无用oc类

查找无用oc类有两种方式,一种是类似于查找无用资源,通过搜索"[ClassName alloc/new"、"ClassName *"、"[ClassName class]"等关键字在代码里是否出现。另一种是通过otool命令逆向__DATA.__objc_classlist段和__DATA.__objc_classrefs段来获取当前所有oc类和被引用的oc类,两个集合相减就是无用oc类。

3. 扫描重复代码

可以利用第三方工具simian扫描。感兴趣的朋友可以自行去尝试,这里不做赘述。

3.2、使用 -Oz 编译参数

Oz 是 Xcode 11 新增的编译优化选项。WWDC 2019 《What's New in Clang and LLVM》 中对 Oz 有过介绍。Oz 的核心原理是对重复的连续机器指令外联成函数进行复用,和“内联函数”的原理正好相反。因此,开启 Oz,能减小二进制的大小,但同时理论上会带来执行效率的额外消耗。对性能(CPU)敏感的代码使用需要评估。

苹果给的参考数据是 4.5% 的包体积收益。

我们在评估了执行效率、堆栈解析、稳定性和编译速度后,对大部分源代码开启了 Oz 编译,包体积减小 2MB 以上。

3.3、使用链接时优化 LTO

图片

Link-Time Optimization 链接时优化,是 Xcode 自带的一个编译/链接参数。根据 WWDC 2016 《What's New in LLVM》的介绍,LTO 对包大小和运行效率都有正向影响。灵犀在编译和链接中均开启 Incremental LTO 后,包体积减小 1MB。

3.4、修正 Exported Symbols 配置

图片

Xcode Build Settings 中的 EXPORTED_SYMBOLS_FILE 配置,控制着 Mach-O 中 __LINKEDIT 段中 Export Info 的信息。动态链接器 dyld 在做符号绑定时,会读取被绑定的动态库或可执行文件的 Export Info 信息,得到一个符号对应的实际调用地址。如果正在被绑定的符号,在目标动态库的 Export Info 中缺失,dyld 则会抛出异常,表现为 App 崩溃。

虽然从原理上看,Export Info 中的信息不可或缺。但是,对于一个 Mach-O 文件来说,并非所有的符号都是需要暴露给其他动态库或可执行文件的。理想情况下,私有的符号应该在编码时就应该以 __attribute__((visibility(hidden))) 修饰。但在历史代码难以逐个添加修饰符的情况下,Exported Symbols 配置给了工程一个维护公有符号白名单的机会。如果填写了有效的 EXPORTED_SYMBOLS_FILE 配置,动态库或者可执行文件会在静态链接时去掉白名单以外的符号,起到缩减包大小、增加逆向难度的作用。

灵犀在使用 Exported Symbols 配置后,包大小减少了 1.1MB。

3.5、__TEXT 段迁移

安装包经过压缩后的 Download Size 若超过 200 MB,在蜂窝网络下载 App 就会受到限制,这对新增会有较大影响。我们探索实践了 __TEXT 段迁移技术:在链接阶段使用 -rename_section 选项将 __TEXT,__text 迁移到 __BD_TEXT,__text,减少苹果对可执行文件的加密范围,提升可执行文件的压缩效率,从而减少 Download Size。

背景知识

1. 下载大小限制

App 大小有下载大小和安装大小的概念。

下载大小是指 App 压缩包(也就是 .ipa 文件)所占的空间,用户在下载 App 时,下载的是压缩包,这样做可以节省流量;当压缩包下载完成后,就会自动解压,解压过程也就是通常所说的安装过程;安装大小就是指压缩包解压后所占用的空间。

安装大小在 App Store 上就可以看见 ,通常它会影响用户的下载意愿:

image.png

而下载大小只有研发人员在 App Store Connect 后台才可以看,用户看不见,它影响的是下载消耗的流量和时长:

image.png

下载大小超过限制,将无法使用蜂窝网络下载 App( iOS 13 之前),会收到文件容量太大的提示,需通过 Wi-Fi 网络下载。如下,为苹果历年来对 App 下载大小限制的变化情况:

  • 2008 年 7 月,搭载了 App Store 的 iPhone 3G 正式发售,下载限制仅为 10 MB

  • 2010 年 2 月,苹果将 iPhone 3G 的下载限制从 10 MB 提升到 20 MB

  • 2012 年 3 月,iOS 5.1 正式版后,下载限制从 20 MB 提升到 50 MB

  • 2013 年 9 月,iOS 7 正式版后,下载限制从 50 MB 提升至 100 MB

  • 2017 年 9 月,iOS 11 正式版后,下载限制从 100 MB 提升至 150 MB

  • 2019 年 5 月,下载限制从 150 MB 提升至 200 MB

  • 2019 年 9 月,iOS 13 正式版后,若下载大小超过 200 MB,用户可选择是否使用蜂窝网络下载

如今,App 下载大小超出 200 MB 时 ,会出现两种情况:

  • iOS 13 以下的用户,无法通过蜂窝数据下载 App
图片
  • iOS 13 及以上的用户,需要手动设置才可以使用蜂窝网络下载 App
图片
2. 可执行文件大小限制

苹果对可执行文件大小有明确限制,超过该限制会导致 App 审核被拒:

ERROR: ERROR ITMS-90122: "Invalid ExecutaBe Size. The size of your app's executaBe file 'News.app/News' is 68534272 bytes for architecture 'arm64', which exceeds the maximum allowed size of 60 MB."

具体限制如下:

  • iOS 7 之前,二进制文件中所有的 __TEXT 段总和不得超过 80 MB

  • iOS 7.X 至 iOS 8.X ,二进制文件中,每个特定架构中的 __TEXT 段不得超过 60 MB

  • iOS 9.0 之后,二进制文件中所有的 __TEXT 段总和不得超过 500 MB

所以对可执行文件大小的优化,一方面可以提升用户下载体验,下载速度更快,使用蜂窝网络下载时更省流量。另一方面对于很多超大型APP来讲,优化可执行文件的大小可以避免被苹果拒审的风险。具体操作如下,只需在 Other Linker Flags 中逐行添加以下配置即可对__ TEXT 段进行迁移:

-Wl,-rename_section,__TEXT,__cstring,__RODATA,__cstring
-Wl,-rename_section,__TEXT,__objc_methname,__RODATA,__objc_methname
-Wl,-rename_section,__TEXT,__objc_classname,__RODATA,__objc_classname
-Wl,-rename_section,__TEXT,__objc_methtype,__RODATA,__objc_methtype
-Wl,-rename_section,__TEXT,__gcc_except_tab,__RODATA,__gcc_except_tab
-Wl,-rename_section,__TEXT,__const,__RODATA,__const
-Wl,-rename_section,__TEXT,__text,__BD_TEXT,__text
-Wl,-rename_section,__TEXT,__textcoal_nt,__BD_TEXT,__text
-Wl,-rename_section,__TEXT,__StaticInit,__BD_TEXT,__text
-Wl,-rename_section,__TEXT,__stubs,__BD_TEXT,__stubs
-Wl,-rename_section,__TEXT,__picsymbolstub4,__BD_TEXT,__picsymbolstub4
-Wl,-segprot,__BD_TEXT,rx,rx

通过对__TEXT 段迁移,对于iOS13以下的机型,下载大小均有8~11M的减少。但是为什么不把 __TEXT 段中的所有 Section 都移走呢?因为剩下的Section大小加起来太小,即便所有的都迁移对包大小几乎没影响,并且有的 Section 是不能移走的,会引起 crash。迁移前后对比:

迁移前


image.png

迁移后


image.png

3.6、二进制段压缩

Mach-O 文件占据了 Install Size 中很大一部分比例,但并不是文件中的每个段/节在程序启动的第一时间都要被用到。可以在构建过程中将 Mach-O 文件中的这部分段/节压缩,然后只要在这些段被使用到之前将其解压到内存中,就能达到了减少包大小的效果,同时也能保证程序正常运行。由于苹果的一些限制,我目前了解到其他公司的做法是压缩 __TEXT,__gcc_except_tab 与 __TEXT,__objc_methtype两个节是没问题的,然后在 _dyld_register_func_for_add_image 的回调中对它进行解压。更多段/节的压缩朋友们可自行尝试。

四、总结

除以上优化外,还有很多优化包大小的方法,例如:属性动态化,减少序列帧动画,重构冗余代码,三方库瘦身等等,有待我们接下来一步步去探索。性能优化是目前各大厂重点发力的地方,在业务渐清晰,没有更多增量开发的时候,抓性能,抓质量是未来的一个发展方向,我们也将在未来一段时间内探索更多性能优化的方向,与大家分享更多我们遇到的问题和解决方案。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容