零. 前言
在OC的开发过程中,要尽量避免分类方法同名,否则会发生什么线上问题也很难被察觉出来(一个踩过坑的菜鸡流泪打下这句话),很多开发的同学即使知道这个原理,但在实际开发过程中还是难免会产生脑子一抽写下分类同名方法的情况。
特别是对于大文件的重构,一不小心就在多个分类上面都写下了系统方法,什么dealloc
,viewDidAppear
,轻则影响到了原有的逻辑,重则因为dealloc
等被重写,没有及时释放对象,引发系统崩溃。
所以写个自动检测脚本还是有必要的,起码能降低影响产品功能的不确定因素= =,这个脚本将会在编译时自动调用,如果检测到分类有同名方法的话,就会编译时报错,将bug扼杀在开发阶段中。
一. 思路
思路很简单,无非就是:
读取工程文件.m、.mm的内容和名字 => 取出所有文件的所有方法名 => 以基础文件名(分类则会去除+xxx)
为key、该文件下所有方法名的数组
为Value存进字典中 => 如果分类的方法名已经在字典中,则视为同名方法,报错
二. 匹配出所有方法
1. 匹配出完整方法
有些内容比较简单就不说了,关键在于用Python如何取出所有文件的所有方法名:
对于OC来说,方法名的情况有以下几种:
- (void)func1 {
}
- (NSString *)func2 {
return nil;
}
- (void) func3:(NSString *)params3 {
}
- (void)func4:(NSString *)param4 func4:(NSString *)param4 {
}
- (void) func5:(NSString *)param5
func5:(NSString *)param5 {
}
- (void)func6:(NSString *)param6
func6:(void(^)(NSString *))param6 {
}
- (void(^)(NSString *))func7:(NSString *)param7
func7:(void(^)(NSString *))param7
func7:(void(^)(NSString *))param7
func7:(void(^)(NSString *))param7
func7:(void(^)(NSString *))param7
func7:(void(^)(NSString *))param7 {
return nil;
}
即 -(xxx)func:(xxx)param func:(xxx)param ... {}
,中间可能会穿插若干个空格或者回车键,而且还有可能有block等带括号的变量,如何准确地忽略空格、回车、block里面的括号等干扰因素,多个参数时准确取出参数方法名,还有一些把+ -
当作减号加号的情况,是解决这个问题的关键所在。
另外,对于OC的方法,我们只需关心几个重点:
-
类方法(+)还是实例方法(-)
-
方法名字是啥(- func1:func2)
而对于参数名、返回类型,我们无需关心,因为对于有且仅有一个名字的类方法/实例方法。
这时候,正则表达式这个大神器又得祭出来了!真是一个一行顶百行的好东西!(虽然这一行可能要想一天= =)
首先匹配到所有的类方法或者实例方法,以+或者-
开头,且以{
结束,中间可能有若干个括号、空格、换行的若干情况的一段文本:
(\+|\-)\s*\([^;]*?\)\s*([^;]*?)\s*\{+?
上面的正则即可匹配到-(xxx)func:(xxx)param func:(xxx)param ... {}
类似的格式,且把带分号的情况踢了出去,以避免加减号干扰到正则了。
2. 对方法进行拆分
方法可能没有参数,也可能有多个参数,这时候我们需要匹配出所有带参数的前缀:
用下面的正则表达式,如果不匹配,则说明这个方法没有参数,如果匹配了,则取每一个匹配的参数进行拼接即可。
(\w*?):*?\(.*?\).*?
这样我们就可以得到所有的方法了
-func1
-func2
-func3:
-func4:func4:
-func5:func5:
-func6:func6:
-func7:func7:func7:func7:func7:func7:
三. 编译时自动运行
其他步骤因为比较简单就不多说了,最后讲一下怎么在编译的时候自动跑这个脚本
我们在Build Phase
加一个.sh脚本,这个.sh脚本用于运行.py脚本,如果python脚本有输出,则会终止编译
# find_error_property.py
ret=$(python "${SRCROOT}/find_category_method_replaced.py")
if [ -n "$ret" ] ; then
echo "error: property declaration" >&2
echo "${ret}"
exit 1
fi
最后把上面提到的几个方法加在分类上面试试:
成功啦!
四. 拓展
如果你实在想在不同分类上面实现同样的方法(比如都想用到dealloc
等方法搞事情),不妨看看这篇文章,大概原理就是自动在同名方法前加个前缀来实现。
五. 成果
通过这个脚本,成功找到了工程中五处被覆盖的分类方法,以及一处迁移时未被及时移除的工程文件。
网友评论