iOS_App_包优化

App安装包(ipa文件)是由资源(图片+文档)和可执行文件(二进制文件)两部分组成,安装包瘦身也是从这两部分进行。

图片

图片基本来说在打包完成后,被压缩很少的,听从苹果的建议,使用 png,并且放到Assets.xcassets目录里面
删除无用的图片. LSUnusedResource软件

  • On Demand Resource
    苹果从iOS 9开始引入了On Demand Resource功能,即一部分图片可以被放置在苹果的服务器上,不随着app的下载而下载,直到用户真正进入到某个页面时才下载这些资源文件。
    我们考虑可以让某些业务仅在iOS 9及以后版本中可用,然后应用On Demand Resource来优化这些业务的资源。
    经过了一段时间的开发实验,一切都如同预期,当我们以为On Demand Resource是一个可行的思路时,我们却发现了一个Xcode巨坑的问题:当工程需要支持iOS9以下系统时,Xcode会在打包完成上传app store时失败。On Demand Resource的想法只能搁置。

  • 修复cocoapods带来的图片重复合并问题, 参考今日头条团队的文章(下文提到)

  • 使用tint color

资源文件

一定要注意,资源文件是否需要编译进入工程,特别是 readme 这种的,不要编译进项目

Xcode编译选项优化:

  • build setting 里 DEAD_CODE_STRIPPING = YES(好像默认就是YES)。 确定 dead code(代码被定义但从未被调用)被剥离,去掉冗余的代码,即使一点冗余代码,编译后体积也是很可观的。
  • Build Settings->Optimization Level有几个编译优化选项,release版应该选择Fastest, Smalllest[-Os],这个选项会开启那些不增加代码大小的全部优化,并让可执行文件尽可能小。
  • Strip Debug Symbols During Copy 和 Symbols Hidden by Default 在release版本应该设为yes,可以去除不必要的调试符号。Symbols Hidden by Default会把所有符号都定义成”private extern”,设了后会减小体积。
  • Strip Linked Product:DEBUG下设为NO,RELEASE下设为YES,用于RELEASE模式下缩减app的大小;

另外注意Xcode里面的Deployment选项,Deployment Postprocessing这个配置项如果使用xcode打包,xcode会默认把这个变量置为YES, 如果使用脚本打包,记得设置。Symbols Hidden by Default设置为YES Make Strings Read-Only 设置为YES

  • 编译选项:LTO,即Link Time Optimization。

苹果在2016年的WWDC What’s new in LLVM中详细介绍了这一功能。LTO能带来的优化有:
(1)将一些函数內联化
(2)去除了一些无用代码
(3)对程序有全局的优化作用
在build setting中开启Link-Time Optimization为Incremental,经测试可缩减安装包大小500KB左右。苹果还声称LTO对app的运行速度也有正向帮助。
但LTO也会带来一点副作用。LTO会降低编译链接的速度,因此只建议在打正式包时开启;开启了LTO之后,link map的可读性明显降低,多出了很多数字开头的“类”(LTO的全局优化导致的),导致我们还经常需要手动关闭LTO打包来阅读link map。

启动速度优化

main()调用之前的耗时我们可以优化的点有:

  • 减少不必要的framework,因为动态链接比较耗时
  • check framework应当设为 optional 和 required ,如果该framework在当前App支持的所有- iOS系统版本都存在,那么就设为required,否则就设为 optional,因为 optional 会有些额外的检查
  • 合并或者删减一些OC类,关于清理项目中没用到的类,使用工具AppCode代码检查功能
  • 删减一些无用的静态变量
  • 删减没有被调用到或者已经废弃的方法 -Wall -Wextra -Weverything Other Warning Flags
  • 将不必须在 +load 方法中做的事情延迟到 +initialize 中
  • 尽量不要用 C++ 虚函数(创建虚函数表有开销)

main()调用之后的加载时间

  • 分析
    在main()被调用之后,App的主要工作就是初始化必要的服务,显示首页内容等。而我们的优化也是围绕如何能够快速展现首页来开展。
    App通常在 AppDelegate 类中的didFinishLaunchingWithOptions: 方法中创建首页需要展示的view,然后在当前runloop的末尾,主动调用CA::Transaction::commit完成视图的渲染。

  • 而视图的渲染主要涉及三个阶段:

    1. 准备阶段 这里主要是图片的解码
    2. 布局阶段 首页所有UIView的 layoutSubViews 运行
    3. 绘制阶段 首页所有UIView的 drawRect: 运行
    4. 再加上启动之后必要服务的启动、必要数据的创建和读取,这些就是我们可以尝试优化的地方
  • main()函数调用之后可以优化的点:

    • 不使用是storyboard、xib,直接视用代码加载首页视图
    • NSUserDefaults实际上是在Library文件夹下会生产一个plist文件,如果文件太大的话一次能读取到内存中可能很耗时,这个影响需要评估,如果耗时很大的话需要拆分(需考虑老版本覆盖安装兼容问题)
    • 每次用NSLog方式打印会隐式的创建一个Calendar,因此需要删减启动时各业务方打的log,或者仅仅针对内测版输出log
    • 梳理应用启动时发送的所有网络请求,是否可以统一在异步线程请求

下面引用 今日头条团队优化

干货|今日头条iOS端安装包大小优化—思路与实践

link map是编译链接时可以生成的一个txt文件,它生成目的就是帮助程序员分析包大小。link map记录了每个方法在当前的二进制架构下占据的空间。通过分析link map,我们可以了解每个类甚至每个方法占据了多少安装包空间。
在编译时开启Xcode build setting中的Write Link Map File开关,Xcode就会生成一份link map文件。
目前已经有不少开源的分析link map的工具,可以输出每个类、每个静态库占用的空间,并进行排序。通过查看link map,我们可以对二进制代码占据的包大小空间有个直观了解,同时在引入第三方库时也可以使用link map作出评估。

如何进行二进制文件优化

技术手段排查冗余代码

没有被引用的类和方法是可以通过技术手段被筛选出来的。
MachO文件中有DATA.objc_classrefs和DATA.objc_selrefs段,分别近似于“被使用的类的集合”和“被使用的方法的集合”。通过取差集的方式可以筛选出未被使用的类和方法。

排查无用类

使用otool命令可查看DATA.objc_classrefs段和DATA.objc_classlist段,两者的差集可以认为是定义了但未使用的类。
不过DATA.objc_classrefs段和DATA.objc_classlist段中都只提供了类在二进制文件中的位置地址,而没有提供类名等可读信息。所以在获取到差集后,还需要结合

otool -o BinaryName

命令的输出,将地址转换成可读的类名。

使用脚本筛选出差集对应的类后,还需要进行一遍人工选择。因为动态使用的类、从nib或storyboard初始化的类以及在同一个文件中定义的多个类会被误判为未使用的类。这需要结合业务进行一次梳理。

排查无用方法

所有已经被实现的方法可以通过linkmap来获取,对linkmap做grep操作即可获得结果:

grep '[+|-]\[.*\s.*\]'

而所有已经被使用的方法可以通过对二进制文件逆向获得。使用otool工具逆向二进制文件的DATA.objc_selrefs 段,提取可执行文件里引用到的方法名:

otool -v -s __DATA __objc_selrefs

使用这种方法取到的差集,还需要排除掉系统API中的protocol,accessor方法等。

使用这个方法,头条排查出了无用方法2000余个,总共累积约2MB,其中最长的方法约7KB。考虑到删除方法的工作量和风险都相对较大,目前我们仅对其中很小一部分进行了删除。

希望对您有所帮助,您的支持将是我莫大的动力!