聊一聊安装包瘦身那些事

大家可以参看微信官方开发公众号:WeMobileDev,大家可以直接在微信公众号里面搜索这个名字,里面的文章写得挺好的,但就是不够细,未能把整个过程讲得更加透彻,仍然需要读者自己去深入研究。不过好在,其为我们指明了一个方向,这个实在不易。

这篇文章挂在这里已经很久了,之前一直想要继续完善,但一直被耽搁了,因为写一些总结文章,我通常会去亲自尝试或者写一些Demo,再看看别人一些优秀的文章,然后才能得出一个总结性的文档,方便后续翻阅。

另外QQ空间团队也开通了官方的微信公众号,大家可以在微信公众号中搜索一下QQ空间终端开发团队,确实有一些干货,值得大家参考和学习(我本人并不在那个团队)。

言归真转,我们团队也为App的瘦身忙活了半个多月了,减少了10多M。做了很多尝试,也非常不容易,首先安装包由两大块组成,资源文件可执行文件


资源文件

资源文件包括图片声音以及其它配置文件。会对资源类文件,我们采取的手段是,删除里面无用的资源文件,以及对资源文件进行压缩。

首先,我们用Python编写了脚本,扫描里面无用的资源文件,现在团队内部好多同事都编写了相关的脚本,基本在几分钟之内就能扫描出结果,但结果还是要再仔细地辨别一下。

我们都知道程序最终会被编译成一个ipa文件,右键显示ipa的内容,我们就可以发现里面有很多资源文件,如图片、声音。通过脚本对引入到工程里面代码文件进行扫描,如果没有任何地方引用,则可以认定其为无用资源文件。具体做法,可以参考我之前的Python学习笔记(下)

另外,我们也可以对资源文件进行压缩,对于png图片资源,我们通常使用PC端一个叫pngout的软件,不过这个软件是有损压缩,所以选择合适的压缩比很重要,当然你可以使用mac上的imageOptim进行无损压缩。或者两者都用。

对于声音文件的压缩,大家可以在Mac OS上使用命令afconvert -f AIFC -d ima4 sound.cf


可执行文件

什么是可执行文件,这儿有一篇文章讲得还不错。大家有时间可以好好阅读一下,其实Mach-O可执行文件主要很为两大块,__DATA段(数据段), __TEXT段(代码段),当然这两者是有着很大的直接关系的,熟悉Objective-C内存布局的同学应该可以有一个比较直观的了解。

如何查看Mach-O可执行文件的大小呢?

我们可以通过LinkMap来进行查看,如何获取它呢,我们可以通过在工程文件设置一个属性来打印这些内容,如下图:
Image

正如上图所示,只需要把Write Link Map File设置为YES,就可以了。Xcode7环境下,其存储目录为

/Users/用户名/Library/Developer/Xcode/DerivedData/项目名称/Build/Intermediates/项目名称.build/Debug-iphonesimulator/项目名称.build/项目名称-LinkMap-normal-x86_64.txt 

关于LinkMap更多的信息大家可以参考iOS可执行文件组成。在这个里面除了可以看到DATA字段与TEXT字段的大小外,它还会列出所有类对象下的成员函数与类函数。

其实这点很重要,因为这样我们就可以知道工程中所有实现的函数了。通过相应的正则表达式,我们就可以提取出函数内容,其正则表达式为[+|-][.+\s(.+)],然后我们通过另外一个强大的反编译工具otool,可以提取出工程中所使用的函数列表(Used Selectors All)。

那什么是otool呢?大家可以自行去google搜索一下,有一篇文章写得还不错,不过还是建议大家再多搜索一些文章进行查看。我们是如何找出工程中所使用的函数列表的呢,其实就是使用命令字otool -V -s __DATA __objc_selrefs 项目.app/项目 | open -f。这里的项目地址指的是项目.app的路径地址,在Xcode7中的路径为

/Users/用户名/Library/Developer/Xcode/DerivedData/项目名/Build/Products/Debug-iphonesimulator/项目名.app/项目名

另外一个要注意的是-V要大写,因为大写和小写的命令是不一样的。

如果代码已经被封装成了framework,依然可以通过这个方法来获取Used Selectors。例如
otool -V -s __DATA __objc_selrefs 项目.app/Frameworks/framework名称.framework/framework名称 | open -f


其实上面说这么多,主要是为一个扫描无用代码的脚本做准备的。下面就聊一聊如何对可执行文件进行瘦身。

  • 扫描无用函数
    上面已经谈到了,我们可以找出工作所有使用的函数的总列表,同时我们也可以找出工程中自己实现的函数总列表,两者一比较就可以找出哪些是没有使用的函数。

  • 扫描空函数
    有些函数只是实现了一个[super function],例如didReciveMemoryWarning这个函数,大部分VC里面都没有做什么事。其实是可以删除的。

  • 扫描无用的类
    有时候整个类都没有人在使用,我上回的Python学习笔记里面有讲述如何扫描,这里就不再赘述了。

  • 删除一些默认方法
    例如,我们创建一个UIView时,在iOS7中其默认就是透明的,所以没有必要再设置clearColor,像这样的代码就可以删除,举一反三的话,还有很多,例如UILabel的居中对齐,lineCount等等。

  • 扫描重复代码
    我之前写过一个扫描重复代码的脚本,大致思路为以函数为单元,每四行每四行进行工程全文检测,如果发现有和其一模一样的代码,则认为有重复代码存在。在脚本中我去除了注释与空格,同时对一行内容少于10个字节的进行过滤。
    扫描出了上千处重复,但维护成本过高,因为需要重构代码,没有删除代码来得直接。

  • @property改造
    去除一些本可以不用@property的项,直接在类里面声明和使用,这样就可以少一些系统自动生成的getter和setter方法。

  • 用C函数进行一些类封装
    例如下面,我们可以通过打断点来查看汇编代码行数减少,这样也可以大量减少安装包。

UILabel *CZ_NewUILabelWithFrameFunc(CGRect frame)
{
    return [UILabel newWithFrame:frame];
}
  • Jce协议转CocoaJce
    这里从原来的C++协议转成了Objective-C,省去了大量的C++中间代码,但C++安装包大小并不大,需要减少很多协议才能产生一个比较明显的效果。


    其它方式

  • Lua插件化
    这个在RunTime实战中曾经谈到过,最近阿里团队也把wax拿过去维护,说是维护,其实他们早就开始在自己修改一些内容了,增加了对64位的兼容,以及block的支持。

  • JSpatch插件化
    这个广研团队的一个哥们整的开源框架,其实在QQ空间内容还有一个类似的叫TPatch,不过现在貌似还没有开源。

  • 优化编译选项
    1、BuildSettings->Optimization Level,Xcode默认设置为“Fastest ,Smallest”,保持默认即可。
    2、Build Settings-> Linking->Dead Code Stripping 设置成 YES
    3、Deployment Postprocessing 设置成YES
    4、Strip Linked Product 设置成YES
    5、工程的Enable C++ Exceptions和Enable Objective-C Exceptions选项都设置为NO。手动管理异常。
    6、symbols hidden by default选项设置为YES。
    7、所有没有使用C++动态特性的lib库(搜索工程没有使用dynamic_cast关键字) Enable C++ Runtime Types 选项设置为NO。


    总结:

    其实安装包的大小的问题应该属于一个产品问题,需要各个角色共同努力才能控制好App安装包大小。光是一味地从开发的角度去减少的话,终究是有尽头的,如果我们能够下架一些根本没有人使用的功能,对于安装包大小的减少还是十分有帮助的。

2015-09-21 22:28923