App性能优化(包瘦身)

一杯茶,一个耳机,一首动态的音乐

目录
一:摘要
二:安装包组成
三:系统优化
四:资源优化
五:可执行文件优化
六:编译器优化
七:拓展

一,摘要

众所周知苹果对iOS App大小有所限制,如果大于200MB使用蜂窝网络下载需要请求许可,对于流量和手机储存敏感的用户很不友好,包的大小甚至影响新用户的转化,所以对于安装包的瘦身是App发展和优化过程中不可避的问题。

二,安装包的组成

1,介绍

iOS安装包就是ipaipa是一个压缩包,用户下载的就是这个压缩包,下载完成就会自动解压,解压的过程也就是通常所说的安装过程,把ipa文件后缀改为zip,然后解压出来再payload中的.app显示包内容就可以查看里面的资源文件。

2,.app里面的主要内容
  • _CodeSignature:存放文件的 hash 列表。里面有一个文件 CodeResources,这个文件是一个属性列表,包含 bundle中所有其他文件的列表。这个属性列表只有一项 files,这是一个字典,键是文件名,值通常是 Base64格式的散列值。如果键表示的文件是可选的,那么值本身也是一个字典,这个字典有一个 hash 键和一个 optional 键(布尔值true)。它的作用是用来判断一个应用程序是否完好无损,能够防止不小心修改或损坏资源文件。

    CodeResources.png

  • 一些bundle文件:bundle是一种标准化的层次结构,保存了可执行代码以及代码所需要的资源。bundle文件可以理解为一个资源包,用于储存图片,音频,文本,nib文件等,方便在其他项目中引用包内资源。bundle包是静态的,不参与编译,也就意味着bundle包中不能包含可执行文件,它仅仅作为资源被解析成为特定的二进制数据。从.app包可以了解到,bundle文件基本都是一些SDK制作的。

  • Assets.car: 把放在Assets.xcassets中的图片(ApplconLaunchImage这两种图片是直接放在包中的)打包后压缩成一个Assets.car的文件,减小包大小。

  • plist: 一些属性列表文件

  • pngjpgmp4json等资源文件

三,系统优化(App Thinning)

1,简介

App Thinning是App Store和操作系统在安装iOS或者watchOS的app的时候通过一系列优化似的app以最小的合适的大小被安装到你的设备上。

2,App Thinning有三种方式
  • App Slicing:会在你向iTunes Connect 上传App后,对App做切割,创建不同的变体,这样就可以适用到不同的设备。
  • Bitcode:针对特定设备进行包大小优化(优化不是很明显)。
  • On-Demand Resources,主要是为游戏多关卡场景服务的,它会根据用户的关卡进度下载随后几个关卡的资源,并且已经过关的资源会被删除,这样就可以减少初装App的包大小。
3,使用
  • 这里大部分工作其实都是由Xcode和App Store来帮你完成的,我们只需要通过Xcode添加xcassets目录,然后将图片添加进来即可,新建一个文件选择 Asset Catalog模板,如图所示:
image.png
  • On-Demand Resources就不多说了,提一点, 在iOS9以后Xcode默认开启了,可以在下图位置进行设置。
image.png

四,资源优化

1,简介

资源优化主要分两部分,一是资源做无损的压缩,二是删除无用的资源,比如图片、音频、视频、配置文件等。

2,图片压缩
  • 如果图片资源小于100k,使用网页工具 Tinyjpg 或者GUI工具 ImageOptim;
  • 如果图片资源大于100k,转换图片格式WebP,转换WebP图片格式工具推荐 iSparta,也可以使用谷歌自己提供的图片压缩工具 cwebp
  • 需要注意的是WebP在cpu消耗和解码时间上会比png高两倍,所以有些需要自己综合考虑权衡利弊,不要为了优化而优化。
3,无用资源删除
  • 推荐使用 LSUnusedResources 工具,很方便(需要二次确认,比如如果你把资源名放入plist文件中,它也会认为没有使用)。

五,可执行文件优化

1,简介

开头已经讲过App安装包主要由资源文件和可执行文件(Mach-O)组成,可执行文件的大小由代码量决定,所以要想对可执行文件瘦身,首先应该做的就是找到并删除无用的代码。

2,Link Map文件分析

Xcode build产生的Link Map文件能比较直观的反映出程序各部分的文件大小情况,对于减少包体积很有帮助。

  1. 获取 LinkMap :将 Build Setting 里的Write Link Map File 设置为 Yes,然后指定 Path to Link Map File 的路径就可以得到每次编译后的 LinkMap 文件了。我们只修改一下生成的 Link Map文件的路径就可以了,后缀名不要修改。
LinkMap
  1. linkMap 分为三部分
  • Object files:代码工程中所有文件编译后的目标文件.o;
LinkMap.png
  • Section: 描述代码段在生成的 Mach-O 里面的偏移位置和大小,包括代码段(__TEXT)和数据段(__DATA)的分布情况。
linkMap.png
  • Symbols:符号相关信息,列出了每个方法、类、block以及它们的大小,第一列Address是在文件中偏移的位置,第二列size是大小,第三列File是对应上面Object files 中的文件编号,第四列Name是文件名。如下图与上图Object files是对应的。
linkMap.png

通过对LinkMap的分析不但可以统计出所有的方法和类,还能清晰的看到代码所占包大小的具体分布,从而有针对性的对代码进行优化。(对Symbols分析还可以通过方法的二进制重排来提成App冷启动速度)。

3,Mach-O文件分析

Mach-OMach Object的缩写,是Mac/iOS上用于储存程序、库的标准格式。一个简单的方法获得该文件->Xcode编译结束会生成一个可执行程序,查找方法如下图:

Mach-O.png

在这个文件夹下找到对应的项目文件名/Build/Products/Debug进入目录就可找到可执行文件了。

Mach-O.png

iOS的方法都是通过objc_msgSend来调用的,而objc——msgSend在Mach-O文件里是通过__objc_selrefs这个section来获取selector这个参数的,所以__objc_selrefs里是被调用的方法,__objc_classrefs 里是被调用过的类,__objc_superrefs是被调用过的super的类,这样就能找出使用过的类和子类,然后对比Link Map文件就可以找出没有用到的类和方法了(OC是动态语言,方法可以在运行时调用,所以该方法找出的无用代码需要我们二次确认才可删除)。

Mach-O.png
4,使用AppCode

以上可以看到,工作量还是不小的,推荐一个神器 AppCode ,AppCode通过静态分析可以快速的帮我们找到没用的类以及方法等,使用起来也特别简单,在菜单栏选择 code ->Inspect Code就可以了。

AppCode.png

分析结果包括:

  • Not implemented methods:没有实现的方法;
  • Key value coding:KVC相关,比如使用KVC访问了@private修饰的成员变量;
  • Unused import statement:无用类引入声明;
  • Unused instance variable :无用的实例变量;
  • Unused method:没有用到的方法;
  • Unused class:没有用到的类;
  • Unused property :没有用到的属性;
  • Unused parameter :无用参数;
  • Unused local variable :无用的局部变量;
  • Unused value :无用的值;
  • Unused macro :无用的宏;
  • Unused global declaration :无用全局声明。

看似AppCode已经把所有工作都做完了,其实不然。下面我们再来列举下AppCode静态检测的问题:

  • 引入的第三方库,比如JsonModel里面定义了未使用的协议会被认为是无用的协议;
  • 如果子类使用了父类的方法,父类的这个方法不会被认为使用了;
  • 通过点的方式使用属性,该属性会被认为没有使用;
  • 使用performSelector方式调用的方法也检测不出来,会被判断为为使用;
  • 运行时声明类的情况检查不出来。比如使用NSClassFromString方式调用的类会被检查出来没有使用;
  • [ [self class] loadData] 这样不指定类名的方式使用的类也会被检测出来是未使用的类,以及UITableView的自定义Cell使用 registerClass,这样的情况也会被认为这个Cell为使用;

所以:AppCode检查完成之后,还是需要我们二次确认才能够删除。

6,编译器优化

Xcode 支持编译器层面的一些优化选项,这些优化选项可以在更快的编译速度、更小的二进制和更快的执行速度之间根据实际需要选择合适的方案。

  • Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default 设置为 YES(新版Xcode默认打开);
  • 去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions 设置为 NO, Other C Flags 添加 -fno-exceptions(Other C Flags 添加 -fno-exceptions 一般只有对程序运行效率及资源占用比较看重的场合才会使用, 如果要做的话最好连libstdc++和其他所有的的c++库都用这两个参数重新编译一遍, 否则只是你自己的程序禁用了这两个特性, 而别的库依然开着, 效果就大打折扣了);
  • Strip Debug Symbols During Copy 设置为YES。这个是将那些拷贝进项目包的第三方库、资源或者Extension的Debug Symbol去除掉(只需在Release模式开启即可,因为开启后不能对第三方库进行断点调试以及符号化);
  • Build Settings -> Optimization Level有几个编译优化选项,release版应该选择Fastest, Smalllest,这个选项会开启那些不增加代码大小的全部优化,并让可执行文件尽可能小(新版Xocde默认已经打开);
  • Enable BitCode设置为YES;

7,拓展

  1. 上面这些工作做完相信App已经很"洁净"了,"瘦身"工作基本完成了,特别对于迭代很久的老项目会有很大的成效。
  2. 对项目进行Archive后会生成 .xcarchive 文件,该文件中包含了AppdsYMS以及其他信息,将 .xcarchive文件上传到 App Store Connect后,苹果会对App中的可执行文件进行DRM加密,然后将App压缩成ipa文件并发布到App Store。加密对可执行文件的大小影响不大,但是它会严重影响可执行文件的压缩效率,导致压缩后的ipa大小增加,也就是我们的安装包增大。
  3. 这种加密使用脱壳工具很容易进行解密。Mach-O文件代码的解密发生在Mach-O文件被加载的时候,由Mach Loader进行。Mach Loader 会读取 Mach-O 中的 LC_ENCRYPTION_INFO 这条Load Command来判断可执行文件是否加密。
  4. 所以可以通过 otool -l 的命令来查看 Mach-O 是否被加密过。
image.png

其中 cryptoff表示加密字段位于文件中偏移 16384个字节;cryptsize表示加密内容长度 16515072字节;cryptid表示加密方法为 1,如果为 0表示不加密(平时打包出来的验证可以发现都为0,那时因为加密是在上传App Store时才做的,怎么获取线上ipa呢,可以参考我另外一篇文章->ipa包获)。

查看 LC_SEGMENT_64__TEXT 段的范围:

image.png

根据上面的结果可以算出加密内容实际上都位于 __TEXT 中,也就是说苹果只会对Mach-O文件中的 __TEXT 段加密,而不会对其他段加密,也就是说,只要能把 __TEXT段中的节移到其他段,就能减少加密范围,从而使压缩效率提升,从而减小下载包大小。

一般来讲,在App中可执行文件占80%的大小,而加密部分可占执行文件的 70%左右,加密会影响 60%左右的压缩率,因此移走该加密部分会提升 34%左右的下载大小(需要注意的是,iOS13已经对下载大小做了优化,所以该方案无法再对iOS13及以上的设备下载大小再做进一步优化)。

需要注意的是,__TEXT 段中所有节都移走对于小型App没有任何问题,但在较大类型的App,还需要注意 Crash链接失效问题。

文章到这里暂告一段落,如果有什么问题或者不足欢迎指正,如果对你有帮助,记得点赞收藏,谢谢!

你可能感兴趣的:(App性能优化(包瘦身))