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 项目避坑:多个分类中方法重复实现检测)