iOS 项目避坑:多个分类中方法重复实现检测

前言

在项目中,我们经常会使用分类 -> category。category在实际项目中一般有两个左右:1.给已有class增加方法,扩充起能力、2.将代码打散到多个文件中,避免因为一个类过于复杂而导致代码篇幅过长(应用于viewController中很好用)

但是 category 也有很多弊端~

首先:它不可以直接添加属性(无法生成成员变量,需要使用对象关联来协助添加属性)

其次:当多人协作开发项目时,一个class可能存在多个category,iOS项目编译时,是按照一定的顺序来编译文件(编译顺序和Compile Sources的文件顺序相关),此时如果两个category实现了相同名字的方法,后编译的category中的方法会将先编译的category中相同名字的方法屏蔽,先编译category的该方法永远不会被执行~

举一个简单的例子:

两个Class的分类:Demo+A、Demo+B

@implementation Demo (A)

- (void)test{
    NSLog(@"A");
}

@end
@implementation Demo (B)

- (void)test{
    NSLog(@"B");
}

@end

这种情况Demo+A中的test方法永远不会被执行到!!
(具体原因这里不做过多介绍,感兴趣的同学可以自己查看category的底层实现原理)

解决

因为OC的这个机制,我发现这块太容易产生错误的,当自己在多个分类写代码的时候,太容易方法名重名了(更何况绝大部分时刻,你是拷贝的别人的代码,就更容易了,咳咳

为了避免这类事情发生,我查了相关资料并写了一个脚本来静态检测一个类的分类是否有重名方法,技术的坑还是要靠技术解决废话不多说,直接上源码:

1、定义白名单
首先定义白名单,我定义了四种类型的白名单,分别是(文件白名单、class白名单、方法白名单、文件夹白名单),白名单中的成员不在检测范围之内

# 文件名白名单,格式:xxx.m
file_white_list = []

# class 白名单,格式:xxx
class_white_list = []

# 方法名白名单,格式:(+/-)xxx(:xxx:xxx:)
method_white_list = ['+load', '-.cxx_destruct']

# 文件夹白名单,格式:xxx
dir_white_list = []
2、遍历目录下所有文件路径
# 遍历目录下所有文件路径
def find_file(rootDir):
    # 获取路径下包含的文件或文件夹的名字的列表
    dirs = os.listdir(rootDir)
    for file in dirs:
        path = os.path.join(rootDir, file)
        file_name = path.split('/')[-1]
        file_type = file_name.split('.')[-1]
        if file_type == 'm' or file_type == 'mm':
            read_file(path)
        # 判断该文件类型是文件夹
        if os.path.isdir(path):
            # 白名单过滤
            if file_name in dir_white_list:
                continue
            find_file(path)

3、遍历读取文件内容

def read_file(path):
    f = open(path)
    # 读取文件内容
    file_string = f.read()
    f.close()
    find_category_same_method(path, file_string)

4、正则匹配获取class名字

def find_category_same_method(file, file_string):
    try:
        file_name = os.path.basename(file).strip()
    except Exception as e:
        print 'error: ' + str(e)
        pass
    if file_name in file_white_list:
        return

    # 正则提取 implementation 中内容,获取类名
    imple_regex = r'@implementation(.*?)@end'
    for imple_string in re.findall(imple_regex, file_string, re.S):
        class_name = imple_string.split('\n')[0]
        find_implementation_same_method(file, class_name, imple_string)

5、正则匹配获取方法名字(生成格式:-/+方法名:)

# 根据实现类的内容进行检查
def find_implementation_same_method(file_path, class_name, imple_string):
    # 匹配OC的方法名,以{结束
    func_regex = r'(\+|\-)\s*\([^;<>=\+\-]*?\)\s*([^;<>=\+\-]*?)\s*\{'
    function_list =  re.findall(func_regex, imple_string, re.S)
    for function in function_list:
        method_type = function[0]
        method = function[-1]
        # 可能有多个方法参数,匹配每一个参数
        split_regex = r'(\w*?)\s*:\s*\(.*?\)'
        result = ""
        sub_methods = re.findall(split_regex, method, re.S)
        if len(sub_methods) == 0:
            result = method
        for sub_method in sub_methods:
            if len(sub_method) > 0:
                result = result + sub_method + ":"
        # 适配返回block且无参数的情况,如:- (void(^)(NSString *))func
        if result.find('(') != -1 and result.rfind(')') != -1:
            result_left = result[:result.find('(')]
            result_right = result[result.rfind(')') + 1:]
            result = result_left + result_right
        result = method_type + result
        handle_method(file_path, class_name, result)

6、方法校验

def handle_method(file_path, class_name, method_name):

    # @implementation xxx { ... }
    if class_name.find('{') != -1:
        class_name = class_name[:class_name.find('{')]

    # @implementation xxx (Category)
    if class_name.find('(') != -1:
        class_name = class_name[:class_name.find('(')]

    # 去除空格
    class_name = class_name.strip()
    class_name = class_name.rstrip()

    # 排除一些implementation
    if class_name in class_white_list:
        return

    # 排除一些方法名
    if method_name in method_white_list:
        return
    method_list = {}
    if method_dict.has_key(class_name):
        method_list = method_dict[class_name]
        if method_list.has_key(method_name) and method_list[method_name] != file_path:
            print("\n#####################\n")
            print 'Class:' + class_name + ' 方法名: [' + method_name + '] 重复实现 \n 路径一:' + file_path + ' \n 路径二:' + method_list[method_name] 
            print("\n#####################\n")
        method_list[method_name] = file_path
    else:
        method_list[method_name] = file_path
    method_dict[class_name] = method_list

7、入口方法及使用

if __name__ == '__main__':
    folder_path_list = sys.argv[1:]
    print folder_path_list
    for path in folder_path_list:
        find_file(path)

python Python文件名.py 文件夹路径

执行结果:

结交人脉

最后推荐个我的iOS交流群:789143298
'有一个共同的圈子很重要,结识人脉!里面都是iOS开发,全栈发展,欢迎入驻,共同进步!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

  • ——点击加入:iOS开发交流群
    以下资料在群文件可自行下载

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

推荐阅读更多精彩内容

  • 前言 使用 Category 为已经存在的类添加方法是我们很熟悉的常规操作,但是如果在 Category 中为类添...
    编程怪才_凌雨画阅读 609评论 0 1
  • 来源作者:字节跳动技术团队 前言启动是 App 给用户的第一印象,启动越慢用户流失的概率就越高,良好的启动速度是用...
    iOS弗森科阅读 1,609评论 0 28
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,454评论 16 22
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,534评论 0 11
  • 可爱进取,孤独成精。努力飞翔,天堂翱翔。战争美好,孤独进取。胆大飞翔,成就辉煌。努力进取,遥望,和谐家园。可爱游走...
    赵原野阅读 2,706评论 1 1