iOS 脚本查看项目中未使用的类

背景:

日常的项目经过长时间的迭代,优化,重构之后,可能会积累一些用不到了的类,长久下去,会影响我们的包大小。
定期的检测,可以在一定程度上控制ipa的增大<话说不是砍需求才是减少代码的最佳方式嘛!哈哈,如果产品同意!>

脚本使用方式

FindClassUnRefs.py & FindAllClassIvars.py 脚本地址

python FindClassUnRefs.py -p /Users/a58/Library/Developer/Xcode/DerivedData/XXX-bqqoxganvkvgwuefbskxsbvnxlnn/Build/Products/Debug-iphonesimulator/XXX.app -w JD,BD,AL

参数说明:
-p Xcode运行之后的,项目Product路径
-w 结果白名单处理,检测结果,只想要以什么开头的类,多个用逗号隔开,比如JD,BD,AL
-b 结果黑名单处理,检测结果,不想要以什么开头的类,多个用逗号隔开,比如Pod,AF,SD
-w 和 -b 不能共存,共存会报错

-p 对应的路径

image.png

运行结果

获取项目中所有的类...
获取项目中所有被引用的类...
获取项目中所有使用load方法的类...
通过符号表中的符号,获取类名...
获取项目中所有类的属性...

查找结果:
只作为其他类的成员变量,不确定有没有真正被使用,请在项目中查看 --------
1 : WBQuotaReduceRateDto
2 : WBAdjustQuota
。。。。
7 : WBActionSheetConfiguration
8 : WBQuotaCardButton

未使用的类 --------
1 : AHKPageResponesRouteModel
2 : WBNavigationBar
3 : AHKPageRouteModel
4 : WBIndexRange
。。。。。
19 : WBStoreService
20 : WBWarningView
21 : AHKFileTool
未使用到的类查询完毕,结果已保存在了find_class_unRefs.txt中,【请在项目中二次确认无误后再进行相关操作】

流程结果图

image.png

最终干掉1,2,3,4,5,6这几个集合,剩下蓝色的底儿就是最终的结果
当然了6会被作为存疑被打印出来。接下来让我们看看具体流程吧

详细工作流程:

流程图


查找项目中未使用类.png

实现分析

通过使用otool工具对编译产生的mach-o文件进行分析,产生结果。
第一步:
分别找到所有类和引用类的集合,然后取差集,初步得到未使用类集合
第二步:
因为一些原因,有些类被引用了没有出现在引用类集合中,而变成未引用类,我们要做的就是找到这些特殊情况,然后排除掉,减少误伤。

原理篇

Mach-O简介

Mach-O文件结构.png

关于Mach-O更详细的每一部分的介绍可以参考
https://www.cnblogs.com/dengzhuli/p/9952202.html
我们也是主要分析__DATA__TEXT,然后通过Symbol解析,再分析

otool简介
otool可以提取并显示iOSMach-O的相关信息,包括头部,加载命令,各个段,共享库,动态库等等。它拥有大量的命令选项,是一个功能强大的分析工具,当然还可以做反汇编的工具使用。
比如:

otool -v -s __TEXT __cstring ClassUnRefDemo001
image.png

接下来按照上面流程图的顺序进行回顾

1、获取项目所有的类符号集合

otool -v -s __DATA __objc_classlist  mach-o Path

获取完毕之后进行倒叙编码

a8 45 00 00 01 00 00 00 ==> 00000001000045a8
image.png

2、获取项目所有被引用的类符号集合

otool -v -s __DATA __objc_classrefs  mach-o Path

获取完毕之后进行倒叙编码

60 47 00 00 01 00 00 00 ==> 0000000100004760
image.png

3、获取项目所有调用load类符号集合

otool -v -s __DATA __objc_nlclslist mach-o Path

获取完毕之后进行倒叙编码

28 48 00 00 01 00 00 00 ==> 0000000100004828
image.png

4、取合集,调用load方法的类也认为是有用的类
将上面的第2步结果和第3步结果求合集,变成所有引用类。
因为调用load方法的类不一定出现在所有引用类中,但是某个类实现了load方法我们认为他一定会使用。

5、取差集,获取未使用到的类符号集合
这一步为了获取项目中所有没有被使用的类

6、通过类的符号,找到类名

nm -nm mach-o Path

通过符号表,找到相关的类名,光看符号我们可不认识这个是啥


image.png

7、查找当前所有类的父类和子类
如果父类没有被使用,子类被使用了,父类是不会出现在第1步的引用类集合的,所以这里要特殊处理。
如果父类在未使用类集合,子类不在未使用类集合,认为父类有被用到。
从未使用类集合将父类删掉
otool -oV 是获取所有的类结构及其定义的方法

otool -oV mach-o Path
image.png

8、检测项目中的静态字符串
如果某个静态字符串在未使用集合,删除当前类<认为使用了runtime的形式调用,事后可以再次确认>
otool -v -s __TEXT __cstring 是获取项目中所有的静态字符串

otool -v -s __TEXT __cstring mach-o Path

为什么要加这层过滤呢,因为项目中的类可能是通过runtime的方式调用的,类名转类,然后使用。这种类是不会出现第1步的。
比如下面的 CViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UIViewController *vc = [[NSClassFromString(@"CViewController") alloc] init];
    [self.navigationController pushViewController:vc animated:YES];
}

image.png

9、检测当前未使用类的分类是否使用了load方法
分类使用了load方法,认为当前类被引用

otool -oV mach-o Path

如果一个分类使用了load方法,那么认为这个类也是被使用的,分类也不能自己裸奔吧。
通过正则找到所有的分类中有load方法的类,进行过滤

image.png

10、黑白名单过滤
黑名单过滤:
我们在查找完毕之后,可能会有很多的类,有些类我们没必要修改也修改不了,比如Pods中的类,或者MJRefresh中检测出一个无用类,我们也不能删除,作为工具类多余的类在以后不一定没用。
我们可以选择不看,比如,输入参数 -b MJ,AF

python FindClassUnRefs.py -p /Users/a58/Library/XXX.app -b MJ,AF

那么我们的结果中会把MJ和AF开头的干掉,方便查看

白名单:
我们只想看我们自己的类,一般项目都会有固定的开头,方便管理
比如,我们的类名都是以WB开头的,输入参数 -w WB,那么结果只会显示WB开头的未使用类

11、查看项目所有的属性
如果未使用类是已使用类的属性,那么在检测结果删除

otool -oV  mach-o Path

如果一个类是另外一个类的属性,除此之外别的地方没有使用过。那么他不会出现在第1步。可能会被认为是未使用类
这个时候可以通过正则找到所有类的属性,如果当前类没有在未使用类集合,并且他的属性中有在未使用类中,那么他应该属于已经用类


image.png

12、检测结果写入日志文件
结果可能过多,终端显示不开,将结果写入文件,方便阅读

13、根据检测结果,在项目中二次确认后处理
检测结果是有偏差的,有些已使用的类仍然不太好检测。
已知的情况:
1、类里面都是C语言的函数,即使方法被调用了,也认为是未使用类
2、storyBoard中的类,都是nib,检测不到
3、作为数组之类的集合类型被引用,并且使用的时候没有体现该类

@property (nonatomic, strong) NSArray<Person *> *perArray;

4、一些类没有进行实例化,而是作为父类进行匹配
比如:
WBBaseHeaderFooterView

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
    if (self.sectionsData.count == 0) {
        return nil;
    }
    else {
        WBBaseHeaderFooterView *view = [tableView dequeueReusableHeaderFooterViewWithIdentifier:self.footerSectionIdentifier];
        WBBaseCellSectionModel *model = self.sectionsData[section];
        if (model.footerModel) {
            if (!view) {
                view = [NSClassFromString(self.footerSectionIdentifier) new];
            }
            [view setFooterModel:model.footerModel];
            return view;
        }
        else {
            return nil;
        }
    }
}

5、A类引用B类,只是#import "B.h",没有调用B的任何方法,会认为B没被用到,这个是正常的。
6、A类引用B类,并且在A类中的方法调用了B类的方法,会认为B类被使用了,不用担心,A类认为未被使用。
7、A类引用B类,B类引用A类,并且相互调用了相互的方法,被认为都被使用过,这个目前还没啥好办法。

如果在检测过程中出现一些误差,还请留言,然后对该脚本进行优化,谢谢!!

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

推荐阅读更多精彩内容