资源优化
1. 去除无用资源
一般都是版本迭代过程中存在的图片资源。
可以借助三方工具来解决: Unused-master LSUnusedResources
通过Unused-master可以查找到一些工程中没有用到的图片,但这个工具并不是百分百正确,在图片删除的时候要慎重看一下。在扫描结果中,查找到了一些命名错误的图片,例如:xxx2x.png、xxx.png这样的图片命名方式会影响APP性能
2. 资源压缩
2.1. 图片无损压缩
这个主要是利用三方工具ImageOptim来实现:
ImageOptim Mac版是一款非常简单的图片大小优化工具。只要拖动图片到软件界面就可以自动把图片的大小进行优化。ImageOptim Mac版对于开发人员和设计人员一定还有用处,如文件的EXIF标签和颜色配置文件等,达到优化减小占用磁盘空间。
图片文件中往往包含一些注释、颜色 Profile 等多余信息,移除后图像质量不变,体积更小载入更快。ImageOptim 以此方式压缩图片,先分析图片,找到最优压缩参数,去除无关信息减小体积,实行无损压缩。
关于图片资源补充:
1.能用缩略图的用缩略图、单一色彩可以用纯色代替
2.尽量使用8-bit图片使用8-bit的PNG图片,比32-bit的图片能减少4倍的压缩率。 由于8-bit的图片支持最多256种不同的颜色,所以8-bit的图片一般只应该用于一小 部分的颜色图片。例如灰度图片最好使用8-bit。
2.2. 音频压缩
参考WWDC中的Audio Development for Games,里面介绍了如何有效的处理音频。常规来说,我们要使用AAC或MP3来压缩音频,并且可以尝试降低一下音频的比特率。有时候44.1khz的采样是没有必要的,稍微低一点的比特率也不会降低音频的质量。
2.3. 查找文件大小
通过查找工程中文件的大小,可以找到一些较大的图片和三方库,视情况压缩或替换这个主要是用命令行,cd到工程目录 然后输入find . -size +100 就会遍历出工程目录下大于100k的文件,如果是+1000 就是大于1000k的,以此类推...
例如:Robin:SimpleFinance zhaojijin$ find . -size +1000
这个方法扫描到的文件并不是完全正确,如上图,这个结果中changeCard_lostCardStepOnePic@3x.png finder中实际大小是854k,但这并不影响使用,大体还是对的
3.不常用资源换为下载
比如自定义字体,可以在APP第一次启动后动态获取
编译优化
1. 去除符号信息
Strip Debug Symbols During Copy 和 Symbols Hidden by Default 在release版本应该设为yes,可以去除不必要的调试符号,当然xcode默认就是yes
2. 开启编译优化
Build Settings->Optimization Level有几个编译优化选项,release版应该选择Fastest, Smalllest,这个选项会开启那些不增加代码大小的全部优化,并让可执行文件尽可能小。
3. 避免编译多个框架(但多个框架在不同机型上运行速度相对快点,iOS9 app thinning)
如果应用需要在多种cpu架构下运行,那么xcode生成的二进制文件会包含对应架构的多个副本,这样可执行文件的大小就会成倍增加。
arm cpu架构可以标识为armvX (64),X代表一个数字,如果不指定是64位,就是指32位。一般来说arm架构都是向后兼容的,armv6的代码可以在armv7上运行,32位的代码可以在64位cpu上运行。(buidsettings->Architectures)
可执行文件优化
在项目里新建一个类,给它添加几个方法,但不要在任何地方import它,build完项目后观察linkmap,你会发现这个类还是被编译进可执行文件了。
linkmap文件是xcode link时产生的中间文件,一般用于调试,可以精确知道某个地址对应的函数。
对此我们可以通过脚本,遍历整个项目的文件,找出所有没有被引用的类文件和没有被调用的方法,在保证没有其他地方动态调用的情况下把它们去掉。如果整个项目历时很长,历时代码遗留较多,这个清理对可执行文件省出的空间还是挺可观的。
可执行文件的组成
XCode开启编译选项Write Link Map File
XCode -> Project -> Build Settings -> 搜map -> 把Write Link Map File选项设为yes,并指定好linkMap的存储位置
编译后,到编译目录里找到该txt文件,文件名和路径就是上述的Path to Link Map File位于~/Library/Developer/Xcode/DerivedData/XXX-eumsvrzbvgfofvbfsoqokmjprvuh/Build/Intermediates/XXX.build/XXX-iphoneos/XXX.build/XXX-LinkMap-normal-armXX.text
其中XXX-eumsvrzbvgfofvbfsoqokmjprvuh的命名是不确定的这个LinkMap里展示了整个可执行文件的全貌,列出了编译后的每一个.o目标文件的信息(包括静态链接库.a里的),以及每一个目标文件的代码段,数据段存储详情。
1.在LinkMap里首先列出来的是目标文件列表:
[0] linker synthesized
[1] /Users/zhaojijin/Library/Developer/Xcode/DerivedData/SimpleFinance-cvxujvtykofyxphauukoxkqhstcn/Build/Intermediates/SimpleFinance.build/Release-iphoneos/SimpleFinance.build/Objects-normal/arm64/UIImageView+HighlightedWebCache.o
[2] /Users/zhaojijin/Library/Developer/Xcode/DerivedData/SimpleFinance-cvxujvtykofyxphauukoxkqhstcn/Build/Intermediates/SimpleFinance.build/Release-iphoneos/SimpleFinance.build/Objects-normal/arm64/YKHomeWaitToBeMatchTitleItem.o
...
[1217] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/usr/lib/libobjc.tbd
[1218] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/usr/lib/libSystem.tbd
[1219] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks//Accelerate.framework/Accelerate.tbd
前面中括号里的是这个文件的编号,后面会用到,像项目里引用到静态链接库里的目标文件都会在这里列出来。
2.接着是一个段表,描述各个段在最后编译成的可执行文件中的偏移位置及大小,包括了代码段(__TEXT,保存程序代码段编译后的机器码)和数据段(__DATA,保存变量值)
# Sections:
# Address Size Segment Section
0x100006660 0x0049CA0C __TEXT __text
0x1004A306C 0x00002424 __TEXT __stubs
0x1004A5490 0x00002418 __TEXT __stub_helper
0x1004A78A8 0x00017790 __TEXT __gcc_except_tab
0x1004BF038 0x0005A3E7 __TEXT __objc_methname
0x100519420 0x00055D9C __TEXT __cstring
0x10056F1BC 0x000082C0 __TEXT __objc_classname
0x10057747C 0x0000A9B8 __TEXT __objc_methtype
0x100581E40 0x0001BAE8 __TEXT __const
0x10059D928 0x0000436E __TEXT __ustring
0x1005A1C98 0x0001235C __TEXT __unwind_info
0x1005B4000 0x000006A8 __DATA __got
0x1005B46A8 0x00001818 __DATA __la_symbol_ptr
0x1005B5EC0 0x0001B710 __DATA __const
0x1005D15D0 0x0003B900 __DATA __cfstring
0x10060CED0 0x00002988 __DATA __objc_classlist
0x10060F858 0x00000028 __DATA __objc_nlclslist
0x10060F880 0x00000320 __DATA __objc_catlist
0x10060FBA0 0x00000018 __DATA __objc_nlcatlist
0x10060FBB8 0x000003D8 __DATA __objc_protolist
0x10060FF90 0x00000008 __DATA __objc_imageinfo
0x10060FF98 0x000FFB30 __DATA __objc_const
0x10070FAC8 0x000145F0 __DATA __objc_selrefs
0x1007240B8 0x00000080 __DATA __objc_protorefs
0x100724138 0x00002A28 __DATA __objc_classrefs
0x100726B60 0x00001B30 __DATA __objc_superrefs
0x100728690 0x00005814 __DATA __objc_ivar
0x10072DEA8 0x00019FA0 __DATA __objc_data
0x100747E48 0x00002E20 __DATA __data
0x10074AC68 0x00002120 __DATA __bss
0x10074D000 0x00000800 __DATA __common
首列是数据在文件的偏移位置,第二列是这一段占用大小,第三列是段类型,代码段和数据段,第四列是段名称。
每一行的数据都紧跟在上一行后面,如第二行__symbol_stub的地址0x00275FD0就是第一行__text的地址0x00002740加上大小0x00273890,整个可执行文件大致数据分布就是这样。
这里可以清楚看到各种类型的数据在最终可执行文件里占的比例,例如__text表示编译后的程序执行语句,__data表示已初始化的全局变量和局部静态变量,__bss表示未初始化的全局变量和局部静态变量,__cstring表示代码里的字符串常量,等等。
3.接着就是按上表顺序,列出具体的按每个文件列出每个对应字段的位置和占用空间
# Address Size File Name
0x100006660 0x00000018 [ 1] -[UIImageView(HighlightedWebCache) sd_setHighlightedImageWithURL:]
0x100006678 0x00000014 [ 1] -[UIImageView(HighlightedWebCache) sd_setHighlightedImageWithURL:options:]
0x10000668C 0x00000058 [ 1] -[UIImageView(HighlightedWebCache) sd_setHighlightedImageWithURL:completed:]
0x1000066E4 0x0000005C [ 1] -[UIImageView(HighlightedWebCache) sd_setHighlightedImageWithURL:options:completed:]
...
同样首列是数据在文件的偏移地址,第二列是占用大小,第三列是所属文件序号,对应上述Object files列表,最后是名字。
1. 去除无用代码
在项目里新建一个类,给它添加几个方法,但不要在任何地方import它,build完项目后观察linkmap,你会发现这个类还是被编译进可执行文件了。
对此我们可以通过脚本,遍历整个项目的文件,找出所有没有被引用的类文件和没有被调用的方法,在保证没有其他地方动态调用的情况下把它们去掉。如果整个项目历时很长,历时代码遗留较多,这个清理对可执行文件省出的空间还是挺可观的。
2. 统计库占用
项目里会引入很多第三方静态库,如果能知道这些第三方库在可执行文件里占用的大小,就可以评估是否值得去找替代方案去掉这个第三方库。我们可以从linkmap中统计出这个信息,利用三方的node.js脚本,可以通过linkmap统计每个.o目标文件占用的体积和每个.a静态库占用的体积,并进行排序
3. 混淆类/方法名
观察linkmap可以发现每个类和方法名都在__cstring段里都存了相应的字符串值,所以类和方法名的长短也是对可执行文件大小是有影响的,原因还是object-c的动态特性,因为需要通过类/方法名反射找到这个类/方法进行调用,object-c对象模型会把类名,方法名列表都保存下来。
可以考虑在编译前把所有类和方法名进行混淆,把长名字替换成短名字,这样做的好处除了缩小体积外,还对安全性有很大提升,别人拿到可执行文件对它class-dump出来的结果都是混淆后的类和方法名,就无法从类和方法名中猜出某个方法是做什么的,就难以挂钩子进行hack。不过这样有个缺点就是crash堆栈反解出来的堆栈方法名会是混淆后的,需要再加一层混淆->原名的转换,实现和使用成本有点高。
4. 减少冗余字符串
代码上定义的所有静态字符串都会记录在在可执行文件的__cstring段,如果项目里Log非常多,这个空间占用也是可观的,也有几百K的大小,可以考虑清理所有冗余的字符串。另外如果有特别长的字符串,建议抽离保存成静态文件,因为AppStore对可执行文件加密导致压缩率低,特别长的字符串抽离成静态资源文件后压缩率会比在可执行文件里高很多。
替换NSLog为DLog:
类、方法名、属性名等命名长短影响包大小:
5. ARC->MRC降低8%空间占用(可忽略,不实用)
homebrew
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"