包瘦身

App Store 官方规定 App 安装包如果超过 150MB,不可以使 OTA(over-the-air)环境下载,也就是只可以在 WiFi 环境下载。并且 App 包体积过大,对用户更新升级率也会有很大影响。
项目由于素材重复体积过大、无用的资源过多、冗余代码过多、重复造轮子等导致包体积过大,所以应用包的瘦身迫在眉睫。

瘦身方案

  • 资源瘦身
  • 基于编译后的瘦身
  • 基于编译过程的瘦身
  • 代码级别瘦身
资源瘦身

统一素材命名规范,去除重复、无用资源文件,解决名字重复问题;
获取资源文件
设置项目工程中的资源类型(jpg/gif/png/webp等)
正在匹配图片名(匹配编号规则),考虑到(image_%d类型)
集合取差集
删除无用图片(NSFileManager)
对于体积过大的资源可以将资源文件放在服务器上,按需下载。

基于编译后的瘦身
Link Map File

经过编译、链接,最终生成一个可执行文件。经过编译器编译会把每个类生成对应的 .o 文件(目标文件)。链接器会把 .o 文件和动态库链接在一起生成一个 mach-o 文件。Link Map File 就是这样一个记录 mach-o 文件格式相关信息的纯文本,里面记录了可执行文件的路径、CPU 架构、目标文件、符号等信息。

Xcode 开启编译选项 Write Link Map File

XCode -> Project -> Build Settings -> 搜 map -> 把 Write Link Map File选项设为 YES,并指定好 LinkMap 的存储位置。

WechatIMG30.jpeg
WechatIMG31.jpeg
LinkMap 文件格式

Path
Path路径记录的是这个 LinkMap 对应的安装包的地址。
Arch
Arch 指的是这个 LinkMap 对应的架构。
Object files
Object files 是编译后生成的文件列表,比如工程里面的 class 文件都编译成了.o文件,像我们比较熟悉的AppDelegate.o 文件等等。还有引进来的几个库,比如UIKit.tbd。第一列的序号是类的编号,通过该编号可以找到对应的类。
Sections
Section 是各种数据类型所在的内存空间,Section 主要分为两大类,__Text和__DATA。__Text指的是程序代码,__DATA指的是已经初始化的变量等。
Symbols
Symbols 这个单词肯定不陌生,什么 Crash 要有对应的符号表,连接器链接的时候经常找不到 Symbols 等。Symbols简单来说就是类名,变量名,方法名等等符号。
Dead Stripped Symbols

image.png
Mach-O

Mach-O 主要由以下三部分组成:
1、Mach-O 头部(Mach Header)。描述了 Mach-O 的 cpu 架构、文件类型以及加载命令等信息。
2、加载命令(load command)。紧跟在头部之后,这些加载指令清晰地告诉加载器如何处理二进制数据,有些命令是由内核处理的,有些是由动态链接器处理的。在源码中有明显的注释来说明这些是动态连接器处理的。
3、Data。Data 中的每个段(segment)的数据都保存在这里,段的概念与ELF文件中段的概念类似。每个段都有一个或多个 Section,它们存放了具体的数据与代码,主要包含代码、数据,例如符号表,动态符号表等等。

image.png
Sections

每个 Section 包含了 Address、Size、Segment
1、__TEXT 包含 Mach header,被执行的代码和只读常量(如C 字符串),只读可执行。
2、__DATA 包含全局变量,静态变量等,可读写。
3、__LINKEDIT 包含了加载程序的元数据,比如函数的名称和地址,只读。
首列是数据在文件的偏移位置,第二列是这一段占用大小,第三列是段类型,代码段和数据段,第四列是段名称。
每一行的数据都紧跟在上一行后面,如第二行 __stubs 的地址 0x10192D49E 就是第一行 __text 的地址 0x1000037D0 加上 size 0x01929CCD,整个可执行文件大致数据分布就是这样。


image.png
Symbols

1、__text代码区
通过地址 0x1000037D0,可以知道它位于__TEXT段的__text区,这段区域存储着代码,通过符号表,根据地址可以对应出源代码,如 +[IMXEnterpriceViewModel creatEnterpriceViewModel]。通过第三列的类编号,可以知道该代码属于 IMXEnterpriceViewModel 类。
2、__objc_methname方法名区
通过地址 0x101B2D1D8,可以知道它位于__TEXT段的__objc_methname区,这段区域存储着方法名,通过符号表,根据地址可以对应出具体的方法名,如 localizedStringForKey:classBundle:。由上面的信息,可以看出方法名越长,最终占用的内存也越大。
3、__objc_classlist类列表区
__objc_classlist 区的 size 值都是8,区域里存储的值都是一个指针,指向了类的虚拟地址(__objc_data区中地址)。
objc_classdata 结构体中保存了类名,方法名,协议名,ivar 指针和属性对应的地址。通过地址,可以找到对应的段和分

image.png
查找未使用的类和方法
  1. 查找无用 selector
    在 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 里的方法加入白名单过滤即可。
  2. 查找无用 oc 类
    查找无用 oc 类有两种方式,一种是类似于查找无用资源,通过搜索"[ClassName alloc/new"、"ClassName *"、"[ClassName class]”等关键字在代码里是否出现。另一种是通过 otool 命令逆向 __DATA.__objc_classlist 段和 __DATA.__objc_classrefs 段来获取当前所有 oc 类和被引用的 oc 类,两个集合相减就是无用 oc 类。
分析大文件

在 Symbols 部分,可以把类编号相同的 size 加起来,算出每个类或库占用的大小。在 Object files 部分根据类的编号可以查出对应的类。

https://github.com/huanxsd/LinkMap

https://github.com/ming1016/SMCheckProject

基于编译过程的瘦身
  • 编写 clang 插件,编译过程中将插件作为 clang 参数载入生成中间文件,通过编写工具分析所有的方法有哪些会被调用;
  • 通过 clang 遍历语法树,获取嵌套访问关系,找出没有被调用的代码;

http://kangwang1988.github.io/tech/2016/11/01/validate-ios-api-using-clang-plugin.html

代码级瘦身
image.png
具体实施方案:
  • 去除重复、无用资源文件,解决名字重复问题。
  • 图片使用.xcassets管理
  • 使用tinypng压缩PNG图片。视频可以通过 Final cut 等软件进行分辨率压缩。音频则降低码率即可。
  • icon 使用 iconfont
  • 非必须资源文件可以放到自己服务器上, 但必用资源文件**需要内置到安装包中。
  • 尽可能的去除无用的代码、控制类名、方法名长度、冗余字符串
  • 去掉 armv7 ,可执行文件以及库会减小,即本地 .ipa 也会减小
  • Generate Debug Symbols(调试符号,debug下关掉)、Dead Code Stripping、Apple Clang - Code Generation、strip linked product等编译优化
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。