iOS安装包瘦身

记录一次安装包大小优化的实践。
维持安装包体积是一个持续的过程,建立预警机制,监控每个版本的体积大小。

资源文件优化
图片资源优化
  1. 推荐通过FengNiao清理无用的图片资源 onevcat/FengNiao
  • swift开发,支持帧动画图片匹配,如image_%d
  • 命令行工具,可以给 Xcode 添加 Run Script,在每次构建的时候自动检测/清理未使用的资源

FengNiao 的基本原理是查找出项目中所有使用到的字符串和项目中所有的资源文件,两者进行匹配(完全匹配和模式匹配,模式匹配支持带数字资源的前缀/中缀/后缀匹配),计算差集就为未使用的资源。

  1. 推荐通过ImageOptim进行图片压缩 ImageOptim
  • 压缩项目中的JPG格式图片能明显减少安装包大小
  • 压缩项目中Assets内的PNG格式图片无明显作用,对于大图甚至产生负优化

Xcode会自动压缩Assets内的PNG格式图片,但是Bundle内的图片资源仍需手动压缩。
对于三方库内的Bundle内的图片建议放置于Assets中。

  1. 大图优化
  • 切分大图,将图片背景、文案改为代码实现
  • 动效资源图片采用雪碧图的方式存储
  • 部分无透明度且无法拆分的背景大图,采用JPG格式并有损压缩至设计师可以接受的范围内
  1. 图标优化
  • 通过修改tintcolor复用单色重复图标
  • 通过旋转复用图标
  1. 推荐使用On-Demand Resources On-Demand Resources Guide

将 App 中的无需立即用到资源放在 App Store 云端上,然后你需要把资源标记为不同的Tag,需要的时候才去下载相应Tag的图片。如游戏中的不同关卡资源。

  1. 建议将webp作为项目图片格式
  • 压缩率高。支持有损和无损2种方式,比如将 Gif 图可以转换为 Animated WebP,有损模式下可以减小 64%,无损模式下可以减小 19%
  • 支持 Alpha 透明和 24-bit 颜色数,不会像 PNG8 那样因为色彩不够出现毛边
  • CUP 消耗和解码时间上会比 PNG 高2倍
视频/音频资源远端化

建议将视频与音频文件放置在服务器,客户端按需下载或者使用流播放。

HTML5远端化

建议将HTML5资源放置在服务器,客户端可以使用离线缓存的方式来缓存网页资源到本地。


代码优化
  1. 扫描未使用代码

基本思路是基于 clang AST,追溯到函数的调用层级,记录所有定义的方法/类和所有调用的方法/类,再取差集。

基本思路是Mach-O 文件中的 (__DATA,__objc_classlist) 段表示所有定义的类, (__DATA.__objc_classrefs) 段表示所有引用的类(继承关系是在 __DATA.__objc_superrefs 中);使用的方法和引用的方法也是类似原理。因此我们使用 otool 等命令逆向可执行文件中引用到的类/方法和所有定义的类/方法,然后计算差集。

  • 基于源码扫描 fui

基本思路是对源码文件进行字符串匹配。例如将 A *a、[A xxx]、NSStringFromClass("A")、objc_getClass("A") 等归类为使用的类,@interface A : B 归类为定义的类,然后计算差集。

  • 通过 AppCode 查找无用代码 AppCode

查找出 AppCode 中无用的类、无用的方法甚至是无用的 import ,但是无法扫描通过字符串拼接方式来创建的类和调用的方法。

  1. Cocoapods 中的优化选项配置

Cocoapods 的 project 文件在每次 pod install 或者 pod update 会重置,所以需要 hook pod install 来设置 Pods 中每个 Target 的编译选项:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['ENABLE_BITCODE'] = 'YES'
      config.build_settings['STRIP_INSTALLED_PRODUCT'] = 'YES'
      config.build_settings['SWIFT_COMPILATION_MODE'] = 'wholemodule'
      config.build_settings['SWIFT_VERSION'] = '5.0'
      if config.name == 'Debug'
        config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Onone'
        config.build_settings['GCC_OPTIMIZATION_LEVEL'] = '0'
        config.build_settings['COPY_PHASE_STRIP'] = 'NO'
        config.build_settings['DEPLOYMENT_POSTPROCESSING'] = 'NO'
        config.build_settings['GCC_GENERATE_DEBUGGING_SYMBOLS'] = 'YES'
      else
        config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Osize'
        config.build_settings['GCC_OPTIMIZATION_LEVEL'] = 's'
        config.build_settings['COPY_PHASE_STRIP'] = 'YES'
        config.build_settings['DEPLOYMENT_POSTPROCESSING'] = 'YES'
        config.build_settings['GCC_GENERATE_DEBUGGING_SYMBOLS'] = 'NO'
      end
    end
  end
end
  1. 静态库瘦身

发布版本中删除 i386、x86_64 是模拟器的指令集,只保留 armv7 和 arm64

  • 静态库指令集信息查看:lipo -info libname.a(或者libname.framework/libname)
  • 静态库拆分:lipo 静态库文件路径 -thin CPU架构 -output 拆分后的静态库文件路径
  • 静态库合并:lipo -create 静态库1文件路径 静态库2文件路径... 静态库n文件路径 -output 合并后的静态库文件径

4.iOS的指令集

  • 如果项目可以仅支持iphone5s及以上设备,去除armv7/armv7s支持
arm64:iPhone6s | iphone6s plus|iPhone6| iPhone6 plus|iPhone5S | iPad Air| iPad mini2(iPad mini with Retina Display)
armv7s:iPhone5|iPhone5C|iPad4(iPad with Retina Display)
armv7:iPhone4|iPhone4S|iPad|iPad2|iPad3(The New iPad)|iPad mini|iPod Touch 3G|iPod Touch4

i386是针对intel通用微处理器32位处理器
x86_64是针对x86架构的64位处理器

模拟器32位处理器测试需要i386架构,
模拟器64位处理器测试需要x86_64架构,
真机32位处理器需要armv7,或者armv7s架构,
真机64位处理器需要arm64架构。

编译优化
  1. Clang/LLVM 编译器优化选项
    Xcode ➡️ Build Setting ➡️Apple Clang - Code Generation ➡️ Optimization Level 设置为Fastest Smallest[-Os]
  • None[-O0]
    Debug 默认级别,不进行任何优化,直接将源代码编译到执行文件中,结果不进行任何重排,编译时比较长。主要用于调试程序,可以进行设置断点、改变变量 、计算表达式等调试工作。
  • Fast[-O,O1]
    最常用的优化级别,不考虑速度和文件大小权衡问题。与-O0级别相比,它生成的文件更小,可执行的速度更快,编译时间更少。
  • Faster[-O2]
    在-O1级别基础上再进行优化,增加指令调度的优化。与-O1级别相,它生成的文件大小没有变大,编译时间变长了,编译期间占用的内存更多了,但程序的运行速度有所提高。
  • Fastest[-O3]
    在-O2和-O1级别上进行优化,该级别可能会提高程序的运行速度,但是也会增加文件的大小。
  • Fastest Smallest[-Os]
    Release 默认级别。这种级别用于在有限的内存和磁盘空间下生成尽可能小的文件。由于使用了很好的缓存技术,它在某些情况下也会有很快的运行速度。
  • Fastest, Aggressive Optimization[-Ofast]
    它是一种更为激进的编译参数, 它以点浮点数的精度为代价。
  1. Swift Compiler - Code Generation
    Xcode -> Build Setting ->Swift Compiler - Code Generation -> Optimization Level 设置为Optimize for Size[-Osize]
  • No optimization[-Onone]:不进行优化,能保证较快的编译速度。
  • Optimize for Speed[-O]:编译器将会对代码的执行效率进行优化,一定程度上会增加包大小。
  • Optimize for Size[-Osize]:编译器会尽可能减少包的大小并且最小限度影响代码的执行效率。
  1. Deployment
    Xcode ➡️ Build Setting ➡️ Deployment
  • Strip Linked Product & Deployment Postprocessing
    Strip Linked Product设置为YES,Deployment Postprocessing设置为NO

在 Archive 的时候 Xcode 总是会把 Deployment Postprocessing 设置为 YES 。所以我们可以打开 Strip Linked Product 并且把 Deployment Postprocessing 设置为 NO,而不用担心调试的时候会影响断点和符号化,同时打包的时候又会自动去除符号信息。

  • Strip Linked Product/Strip Debug Symbols During
    仅在Release环境设置为YES

与 Strip Linked Product 类似,但是这个是将那些拷贝进项目包的三方库、资源或者 Extension 的 Debug Symbol 去除掉,同样也是使用的 strip 命令。这个选项没有前置条件,所以我们只需要在 Release 模式下开启,不然就不能对三方库进行断点调试和符号化了

去掉不必要的符号信息,可以减少可执行文件大小,但去除了符号信息之后我们就只能使用 dSYM 来进行符号化。

  1. Link-Time Optimization
    Xcode ➡️ Build Setting ➡️Apple Clang - Code Generation ➡️Link-Time Optimization设置为Incremental

Link-Time Optimization 是 LLVM 编译器的一个特性,用于在 link 中间代码时,对全局代码进行优化。这个优化是自动完成的,因此不需要修改现有的代码;这个优化也是高效的,因为可以在全局视角下优化代码。
开启这个优化后,一方面减少了汇编代码的体积,一方面提高了代码的运行效率.

  • 多余代码去除(Dead code elimination):如果一段代码分布在多个文件中,但是从来没有被使用,普通的 -O3 优化方法不能发现跨中间代码文件的多余代码,因此是一个“局部优化”。但是Link-Time Optimization 技术可以在 link 时发现跨中间代码文件的多余代码。
  • 跨过程优化(Interprocedural analysis and optimization):这是一个相对广泛的概念。举个例子来说,如果一个 if 方法的某个分支永不可能执行,那么在最后生成的二进制文件中就不应该有这个分支的代码。
  • 内联优化(Inlining optimization):内联优化形象来说,就是在汇编中不使用 “call func_name” 语句,直接将外部方法内的语句“复制”到调用者的代码段内。这样做的好处是不用进行调用函数前的压栈、调用函数后的出栈操作,提高运行效率与栈空间利用率
  1. Asset Catalog Compiler
    Xcode ➡️ Build Setting ➡️Asset Catalog Compiler ➡️Optimization设置为space

参考资料:
App Thinning in Xcode
网易云安装包瘦身
微信安装包瘦身
iOS 瘦身之道

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • 安装包过大,不利于市场人员做推广,最近做了 iOS 安装包瘦身的技术研究和实践。iOS APP经过编译,打包文件中...
    iLees阅读 1,365评论 0 5
  • 我被婚闹了! 我不是伴娘,我只是以同学的身份来参加婚礼,竟然莫名其妙的被婚闹了! 01 小A是我们宿舍第一个要准备...
    潇洒喵喵阅读 1,070评论 2 0
  • 我记不清简书在我的手机待了多长时间,唯一的意识就是它待了很久,但我很少打开它,每天浏览的也就是那几个固定的软...
    顾予浓啊阅读 174评论 0 1