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

hjsw002 8年前
   <p> </p>    <p>最近项目末期, 我们团队为了ipa的大小使用不少的体积减小的方法, 除了一些常规的方法之外, 我分享一下自己研究出来的新思路。</p>    <p>首先我们来简单的介绍一下mach-O。</p>    <h2>什么是mach-O?</h2>    <p>Mach-O格式全称为Mach Object文件格式的缩写,是mac上可执行文件的格式,类似于windows上的PE格式 (Portable Executable ), linux上的elf格式 (Executable and Linking Format)。</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/baa287edcbd8a9af0ee122bc1adc5636.jpg"></p>    <p>上面第一个图是苹果给出的mach-O格式的示意图,而第二个图是我们使用machOView来分析某个可执行文件中的armv7的格式。可以看出他们两者的关系是对应的。</p>    <p>在machO这其中包含了很多的有效的信息,包括字符串,代码段,oc类,oc协议等各种的信息,利用这些信息我们也做到分析代码或者程序逻辑的作用,比如,下面这个数据就是我从这个machO文件里面导出来的,获取到了某个framework一个OC类中的所有基本元素。</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/fc46a0161b82982546f3fc89ae57e410.png"></p>    <h2>那什么又是FatFile/FatBinary</h2>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/3260687ed4d8cdbd3a1d67872145ca93.png"></p>    <p>简单来说,就是一个由不同的编译架构后的Mach-O产物所合成的集合体。例如上面我就只截取armv7的Mach-O格式座位示例, 而实际上常用的还有arm64/x86_64/i386等格式。</p>    <p>而实际上,包括我们使用的那些framework,大多数也是的。比如下图我们继续用machOView分析一下。</p>    <p>可以看到arm64/armv7架构的存在。</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/b0969ac0d05256b8b72eaad2c5945be8.png"></p>    <h2>FrameWork跟最终可执行文件的区别在哪里?</h2>    <p>这里我们先随便写一个简单的framework, 在这个framework中我们实现了两个oc类,如下图所示:</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/e22e7531f7e164396cc5beba60ea8086.png"></p>    <p>紧接着我们干净用machOView来看看这个新鲜出炉的framework,可以看到该FrameWork在armv7的格式下,里面存在多个.o文件。</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/d3dea2f4317fe17fd7735b1d841a22de.png"></p>    <p>如果我们选择其中一个继续看看的话,你就会看到一个完整的Mach-O格式的文件。</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/8f8c492cd71e1885d65dc967aa47a00a.png"></p>    <p>由此我们可以得知frameWork也是另外一种情况的Mach-O集合体,是由多个不同的子mach-O文件所组合而成的,他们可以单独的拆开,而可执行文件则把同一架构下的所有Mach-O文件都进行了合并,他们不能拆开,如果想要更加清晰的定义的话,可以去研究一下苹果的定义,这里不做过多的阐述。</p>    <h2>我们能做什么?</h2>    <p>可能到这里你还有点乱,没关系,我们直接来拆开一个framework给大家看看!</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/ee66b53d724bb28d6b4f425bd71bc055.png"></p>    <p>到了这一步,我们就已经知道了我们能把FrameWork中的各个子Mach-O文件拆开, 那么我们能不能把这些Mach-O文件中有效的部分重新组装一下, 生成新的一个FrameWork呢?</p>    <p>这必须是可以的,但是其实最重要的关键是在于怎么去确定这个Mach-O文件有没有被我们的程序使用到。</p>    <h2>怎么确定一个Mach-O有没有被使用到?</h2>    <p>我们直接再来一个简单的demo,尝试一下</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/001454964f616e94e257c758451d4418.png"></p>    <p>此时进行编译</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/7fe3bfb7d562f0a96aca01cc085a8d89.png"></p>    <p>成功.....</p>    <p>然后拆分老的framework文件, 删掉拆分得到的MachOClassA, 并且用剩下的mach-O文件合并生成一个新的framework, 替换到工程中去并进行编译</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/d16e255dd80373e6bf7a1abe24071e84.png"></p>    <p>你没看错, 他确实是失败了, 如果在程序中代码直接使用了某些类或者某些方法, 而其mach-O文件不存在的情况下, 会导致编译不过(找不到对应的方法), 这也就是说, 我们能够使用最简单粗暴的方法来判断这个machO文件是不是被需要的!</p>    <p>不过, 需要注意的是, category的实现方式是不一样的,故如果我们删除了category的方法, 但是直接把Mach-o删除的话, 编译时是不会报错的。有兴趣的同学可以自己去看看oc中关于category的实现。</p>    <p>另外还有在程序运行中动态使用的performselector方法(可以通过查询字符串列表排除)。</p>    <p>下面是相关的流程图。</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/bdf12cd22668bcd99a2de523663e28dc.png"></p>    <h2>怎么把Mach-O的删除工具应用到XCode中去</h2>    <p>现在我们已经通过编译的手段获得了一堆mach-O文件, 但是很多都是pod中引进的, 这个时候我们需要在代码编译器执行删除.o文件的脚本 刚好Xcode确实有这么一个地方可以设置</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/1650507e1bf1111b3cb0cd9b3dc44378.png"></p>    <h3>成果</h3>    <p>在debug模式下大概减少了0.5M, 实际二进制文件减小大概1.2M, 如果计算到最终提交到苹果并且经过DRM加密后, 预计可以减小1M左右。</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/ad7d7245e218986a5ad080241ebe9e32.png"></p>    <h3>PS</h3>    <p>category是需要过滤的, 这货有点特别</p>    <p>删除找出来的.o文件之后, 可能会引起一些特殊的情况, 当然一般是crash, 因为有一些特别的代码他们用法并不是直接引用某个方法, 而是通过NSString相关的方法来获得Sel或者Class</p>    <p>把查找.o文件的操作放在本地, 而在编译器上进行编译的时候就直接执行删除, 不占用编译器的时间(我们的项目要使用六个小时以上的时间来进行查找)</p>    <p>建议进行操作再跑一遍回归测试, 确保各个功能模块正常</p>    <h2>其他实践与猜想以及做过的尝试</h2>    <p>.o文件其实是可以直接引进到工程里面直接编译的, 也就是说其实可以把frameWork拆开, 然后加到工程中, 一样能够正常使用</p>    <p>一开始其实是想把.o文件中__text段中无用的函数进行删除, 但发现流程过于复杂, 而暂时放弃, 查找程序中无用函数的方法以后有机会再进行分享(如果这个成功的话, 估计会减小至少3~4M左右的ipa大小), 附上查找到的程序中无用方法结果的示例</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/25a63e9080f7e592f0af2c065b0fc57c.png"></p>    <p>其实看了上面那种方法之后, 我们紧接着又能想到, 暴力的将.m文件中的代码删除, 然后看看哪些工程中可见的代码是可以删除的(ps. 主要针对非framework, 另外也同样需要注意category以及performselector的问题, 需要配合查找字符串列表一起进行, 或者手工进行判断)。</p>    <p>来自: <a href="/misc/goto?guid=4959671928443567413" rel="nofollow">http://www.infoq.com/cn/articles/ios-thinning-delete-unnecessary-mach-o</a></p>