iOS 瘦身之删除 FrameWork 中无用 mach-O 文件

阅读数:5379 2016 年 4 月 30 日 00:04

最近项目末期, 我们团队为了 ipa 的大小使用不少的体积减小的方法, 除了一些常规的方法之外, 我分享一下自己研究出来的新思路。

首先我们来简单的介绍一下 mach-O。

什么是 mach-O?

Mach-O 格式全称为 Mach Object 文件格式的缩写,是 mac 上可执行文件的格式,类似于 windows 上的 PE 格式 (Portable Executable ), linux 上的 elf 格式 (Executable and Linking Format)。

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

上面第一个图是苹果给出的 mach-O 格式的示意图,而第二个图是我们使用 machOView 来分析某个可执行文件中的 armv7 的格式。可以看出他们两者的关系是对应的。

在 machO 这其中包含了很多的有效的信息,包括字符串,代码段,oc 类,oc 协议等各种的信息,利用这些信息我们也做到分析代码或者程序逻辑的作用,比如,下面这个数据就是我从这个 machO 文件里面导出来的,获取到了某个 framework 一个 OC 类中的所有基本元素。

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

那什么又是 FatFile/FatBinary

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

简单来说,就是一个由不同的编译架构后的 Mach-O 产物所合成的集合体。例如上面我就只截取 armv7 的 Mach-O 格式座位示例, 而实际上常用的还有 arm64/x86_64/i386 等格式。

而实际上,包括我们使用的那些 framework,大多数也是的。比如下图我们继续用 machOView 分析一下。 

可以看到 arm64/armv7 架构的存在。

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

FrameWork 跟最终可执行文件的区别在哪里?

这里我们先随便写一个简单的 framework, 在这个 framework 中我们实现了两个 oc 类,如下图所示:

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

紧接着我们干净用 machOView 来看看这个新鲜出炉的 framework,可以看到该 FrameWork 在 armv7 的格式下,里面存在多个.o 文件。

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

如果我们选择其中一个继续看看的话,你就会看到一个完整的 Mach-O 格式的文件。

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

由此我们可以得知 frameWork 也是另外一种情况的 Mach-O 集合体,是由多个不同的子 mach-O 文件所组合而成的,他们可以单独的拆开,而可执行文件则把同一架构下的所有 Mach-O 文件都进行了合并,他们不能拆开,如果想要更加清晰的定义的话,可以去研究一下苹果的定义,这里不做过多的阐述。

我们能做什么?

可能到这里你还有点乱,没关系,我们直接来拆开一个 framework 给大家看看!

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

到了这一步,我们就已经知道了我们能把 FrameWork 中的各个子 Mach-O 文件拆开, 那么我们能不能把这些 Mach-O 文件中有效的部分重新组装一下, 生成新的一个 FrameWork 呢?

这必须是可以的,但是其实最重要的关键是在于怎么去确定这个 Mach-O 文件有没有被我们的程序使用到。

怎么确定一个 Mach-O 有没有被使用到?

我们直接再来一个简单的 demo,尝试一下

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

此时进行编译

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

成功.....

然后拆分老的 framework 文件, 删掉拆分得到的 MachOClassA, 并且用剩下的 mach-O 文件合并生成一个新的 framework, 替换到工程中去并进行编译

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

你没看错, 他确实是失败了, 如果在程序中代码直接使用了某些类或者某些方法, 而其 mach-O 文件不存在的情况下, 会导致编译不过 (找不到对应的方法), 这也就是说, 我们能够使用最简单粗暴的方法来判断这个 machO 文件是不是被需要的!

不过, 需要注意的是, category 的实现方式是不一样的,故如果我们删除了 category 的方法, 但是直接把 Mach-o 删除的话, 编译时是不会报错的。有兴趣的同学可以自己去看看 oc 中关于 category 的实现。

另外还有在程序运行中动态使用的 performselector 方法(可以通过查询字符串列表排除)。

下面是相关的流程图。

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

怎么把 Mach-O 的删除工具应用到 XCode 中去

现在我们已经通过编译的手段获得了一堆 mach-O 文件, 但是很多都是 pod 中引进的, 这个时候我们需要在代码编译器执行删除.o 文件的脚本 刚好 Xcode 确实有这么一个地方可以设置

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

成果

在 debug 模式下大概减少了 0.5M, 实际二进制文件减小大概 1.2M, 如果计算到最终提交到苹果并且经过 DRM 加密后, 预计可以减小 1M 左右。

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

PS

category 是需要过滤的, 这货有点特别

删除找出来的.o 文件之后, 可能会引起一些特殊的情况, 当然一般是 crash, 因为有一些特别的代码他们用法并不是直接引用某个方法, 而是通过 NSString 相关的方法来获得 Sel 或者 Class

把查找.o 文件的操作放在本地, 而在编译器上进行编译的时候就直接执行删除, 不占用编译器的时间(我们的项目要使用六个小时以上的时间来进行查找)

建议进行操作再跑一遍回归测试, 确保各个功能模块正常

其他实践与猜想以及做过的尝试

.o 文件其实是可以直接引进到工程里面直接编译的, 也就是说其实可以把 frameWork 拆开, 然后加到工程中, 一样能够正常使用

一开始其实是想把.o 文件中 __text 段中无用的函数进行删除, 但发现流程过于复杂, 而暂时放弃, 查找程序中无用函数的方法以后有机会再进行分享(如果这个成功的话, 估计会减小至少 3~4M 左右的 ipa 大小), 附上查找到的程序中无用方法结果的示例

(点击放大图像)

iOS瘦身之删除FrameWork中无用mach-O文件

其实看了上面那种方法之后, 我们紧接着又能想到, 暴力的将.m 文件中的代码删除, 然后看看哪些工程中可见的代码是可以删除的(ps. 主要针对非 framework, 另外也同样需要注意 category 以及 performselector 的问题, 需要配合查找字符串列表一起进行, 或者手工进行判断)。


作者

靖明 @阿里移动安全


感谢徐川对本文的审校。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

收藏

评论

微博

用户头像
发表评论

注册/登录 InfoQ 发表评论