long time no see,最近在总结一些平(应)常(付)用(面)到(试)的知识点,今天就跟你们聊了聊App体积优化这个事儿。python
别问!问就是为了应付面试。git
哈哈,开个玩笑。你们生活中都会遇到一个场景,在某个须要紧急打开App的时候,发现使用的App半天打不开!WTF!而另一款相同功能的App却能够瞬间打开。哪一个App可以挽留更多的用户就不言而喻了吧!github
借用某个游戏里边人物的一句话:"时间就是金钱,个人朋友!"web
下边咱们先查看一个的思惟导图:面试
思惟导图已经总结目前我已经知道的而且能够落地的优化方式。算法
如图所示APP体积优化包括两部分:资源瘦身和代码瘦身。bash
下面我将使用APPReduction这个简单的demo实地操做一下。须要的同窗能够到gayhub下载一下。网络
删除资源多线程
由于旷日持久的业务代码堆砌,工程内极可能会堆积许多无用的图片,而这些图片却能实实在在的增长App的体积。而咱们彻底能够借助工具LSUnusedResources进行资源文件的删除工做。架构
在RedutionViewController
中,configImageTest
方法中你会找到image图片的代码调用
- (void)configImageTest{
[UIImage imageNamed:@"cooker"]; [UIImage imageNamed:@"driver"]; [UIImage imageNamed:@"function"]; [UIImage imageNamed:@"header"]; // [UIImage imageNamed:[@"think_"stringByAppendingFormat:@"005"]]; // [UIImage imageNamed:[@"think_"stringByAppendingFormat:@"006"]]; // [UIImage imageNamed:[@"think_"stringByAppendingFormat:@"007"]]; } 复制代码
当咱们使用LSUnusedResources
进行图片资源检察的时候会发现未使用的图片。
可是这里须要注意最好进行一下手动的检察避免出现误删的状况,而且若是代码仅仅是注释掉,程序并不会认为资源是废弃的。
压缩资源
请以合理且合法的方式多跟UI多谈谈,在切图片的时候请按须要切图,没必要要每一张都是高清无码,在可接受的范围内能够压缩资源图片!并且笔者认为全部跟图片相关的改变质量的问题都须要经手UI,切记不要本身瞎搞。
大图片资源
不常常用到的大图资源能够采起下载的方式加载到APP上,不是非要打包到ipa里边!能跟产品聊聊的问题别死磕代码
当咱们的App被打包成ipa的时候,代码会被打包成一个一个个的.o文件,而这些.o文件组成了MachO,而系统在编译MachO文件的时候会生成一个附带的文件LinkMap。
LinkMap的组成
LinkMap由Object File、Section、Symbol三部分组成,描述了工程全部代码的信息。能够根据这些信息针对性的去优化空间。
LinkMap的获取
1.在XCode中开启编译选项Write Link Map File \nXCode -> Project -> Build Settings -> 把Write Link Map File
设置为YES
2.在XCode中开启编译选项Write Link Map File \nXCode -> Project -> Build Settings -> 把Path to Link Map File
的地方设置好地址
3.运行项目在地址位置将生成.txt的文件
LinkMap的分析
1.借助工具LinkMap解析工具,咱们能够分析每一个类占用的大小
2.针对性的进行代码的体积的优化,好比三方库占用空间巨量,有没其余的替代方案。在取舍两个相同库的时候也能够根据体积的比重作出取舍。
看到这里咱们已经能够从宏观的角度上获取到须要优化哪些部分的代码,可是微观角度哪些是无用的类哪些是无用的方法,须要咱们进一步从MachO的层面上去分析。
MachO文件能够说是App编译以后的最重要的部分,经过MachOView这个软件咱们能够更加直观看到MachO的组成。若是你的MachOView运行的时候出现崩溃请按照这篇文章进行修改。
__objc_selrefs:记录了几乎全部被调用的方法
__objc_classrefs和__objc_superrefs:记录了几乎全部使用的类
__objc_classlist:工程里全部的类的地址
删除无用的类
MachO文件中__objc_classrefs
段记录里了引用类的地址,__objc_classlist
段记录了全部类的地址,咱们能够认为取二者的差值就能够得到未使用类的地址,而后进行符号化,就能够取得未使用类的信息。
你们可使用classunref这个工具实现未使用类的查找。
若是对实现感兴趣的同窗能够拜读大佬的文章iOS代码瘦身实践:删除无用的类
删除未使用的方法
当咱们将无用的类删除完毕以后,在已经使用的类里边颇有可能依然会有未使用的方法。
前边咱们已经提到过LinkMap中保存了工程的信息,而咱们全部已经被包含到项目中的方法能够经过LinkMap获取。
在classunref的启发下,笔者利用python实现了未使用方法的自动化方式
由于py实在不太熟悉加上笔者比较懒,请你们忽略一些语法、接口设计不规范等等的问题
1.使用指令grep '[+|-]\[.*\s.*\]' xxx-linkMap.txt
指令咱们获得全部被包含到工程到项目中的代码
// 获取全部的方法
def method_readRealization_pointers(linkMapPath,path):
# all method lines = os.popen("grep '[+|-]\[.*\s.*\]' %s" % linkMapPath).readlines() // 须要忽略的方法 lines = method_ignore(lines,path); pointers = set() for line in lines: line = line.split('-')[-1].split('+')[-1].replace("\n","") line = line.split(']')[0] line = str("%s]"%line) pointers.add(line) if len(pointers) == 0: exit('Finish:method_readRealization_pointers null') print("Get all method linkMap pointers...%d"% len(pointers)) return pointers 复制代码
2.考虑到你们项目中使用了大量的三方库,而三方库的方法有许多并未使用,因此经过method_ignore
方法进行忽略,这样获取的差值的集合中就不会包括三方库的未使用方法
def method_ignore(lines,path):
print("Get method_ignore...") effective_symbols = set() // 获取全部须要忽略类名的前缀(例如YYModel 会之前缀YY的方式做出忽略) prefixtul = tuple(class_allIgnore_Prefix(path,'','')) getPointer = set() // 此处是为了忽略Setter Getter方法 for line in lines: classLine = line.split('[')[-1].upper() methodLine = line.split(' ')[-1].upper() if methodLine.startswith('SET'): endLine = methodLine.replace("SET","").replace("]","").replace("\n","").replace(":","") print("methodLine:%s endLine:%s"%(methodLine.lower(),endLine.lower())) if len(endLine) != 0: getPointer.add(endLine) getPointer = list(set(getPointer)) getterTul = tuple(getPointer) for line in lines: classLine = line.split('[')[-1].upper() methodLine = line.split(' ')[-1].upper() if (classLine.startswith(prefixtul)or methodLine.startswith(prefixtul) or methodLine.startswith('SET') or methodLine.startswith(getterTul)): continue effective_symbols.add(line) if len(effective_symbols) == 0: exit('Finish:method_ignore null') return effective_symbols; 复制代码
3.使用指令otool -v -s __DATA__objc_selrefs
指令咱们能够获得全部已经被实现的方法
def method_selrefs_pointers(path):
# all use methods lines = os.popen('/usr/bin/otool -v -s __DATA __objc_selrefs %s' % path).readlines() pointers = set() for line in lines: line = line.split('__TEXT:__objc_methname:')[-1].replace("\n","").replace("_block_invoke","") pointers.add(line) print("Get use method selrefs pointers...%d"% len(pointers)) return pointers 复制代码
3.利用步骤1和步骤2的差值能够获取到还未使用的方法。
def method_remove_Realization(selrefsPointers,readRealizationPointers): if len(selrefsPointers) == 0: return readRealizationPointers if len(readRealizationPointers) == 0: return null methodPointers = set() for readRealizationPointer in readRealizationPointers: newReadRealizationPointer = readRealizationPointer.split(' ')[-1].replace("]","") methodPointers.add(newReadRealizationPointer) unUsePointers = methodPointers - selrefsPointers; dict = {} for unUsePointer in unUsePointers: dict[unUsePointer] = unUsePointer for readRealizationPointer in readRealizationPointers: newReadRealizationPointer = readRealizationPointer.split(' ')[-1].replace("]","") if dict.has_key(newReadRealizationPointer): dict[newReadRealizationPointer] = readRealizationPointer str = dict[newReadRealizationPointer] return list(dict.values()) 复制代码
感兴趣的同窗能够在这里下载到修改版的 ONLClassMethodUnref
若是你的工程不够巨大,借助AppCode这个工具的静态分析也能够查找到未使用的代码。方法极为简单打开AppCode->选择Code->点击Inspect Code---等待静态分析
可是笔者仍是在这里须要做出提醒,即便你用到了上边的全部方式,基于动态语言的特性,咱们仍然不可以找出全部未使用的代码,而且在删除代码的时候仍然须要当心翼翼!切勿多删!记得作回归测试!
> 做为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个个人iOS交流群:761407670 进群密码'博客',无论你是小白仍是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 你们一块儿交流学习成长!
提供逆向安防、Swift、算法、架构设计、多线程,网络进阶,还有底层、音视频、Flutter等资料