美文网首页程序员ios专题移动开发技术前沿
使用Python脚本分析iOS工程跨模块引用

使用Python脚本分析iOS工程跨模块引用

作者: hi_xgb | 来源:发表于2018-06-13 00:08 被阅读154次
    image

    一个结构清晰的工程不仅有利于团队新成员快速熟悉,还有利于功能的稳定迭代。但是随着项目的不断迭代,整体结构往往趋向于混乱,这就需要我们在结构上做一些控制,一是编写开发规范限制跨模块引用,二是编写工具脚本不定期检测整个工程引用情况,保障引用的合理性。

    查找资料发现 https://github.com/nst/objc_dep,该工具能分析出所有引用关系并且结合 GraphViz 或 OmniGraffle 图像化输出结果。但是该工具无法分析跨模块引用情况,以下是我用Python基于字符串匹配编写的一个小工具,可以扫描出指定目录下的跨模块引用情况,下面具体介绍一下整体思路。

    分析流程

    image

    上图列出了完整的分析过程,拆解成以下几个步骤:

    1. 根据指定的目录,将该目录下的所有第一级目录作为模块名,用Map的结构构建文件关系,结果类似 {"ModuleA":["File1","File2",...]} {"ModuleB":["File1","File2",...]}

    2. 从第一步里的每个具体文件中提取出引用列表,结果类似{"File1":["Import2","Import3","Import4",...]},表示文件1引用了2、3、4,此时还不知道2、3、4是否是跨模块引用

    3. 将第二步中提取出的引用文件和第一步中的文件关系遍历比较,获取出引用文件的归属模块

    4. 构造数据结构存储引用关系

    5. 格式化打印出跨模块引用关系

    代码实现

    构建文件关系
    #build module and files relationship 
    #key:module_name value:file_list
    def get_file_relation_map(path):
        if path.endswith('/'):
            path = path[:-1]
        file_map = {}
        for dirName, subdirList, fileList in os.walk(path):
            for fname in fileList:
                if fname.endswith('.h') or fname.endswith('.m') or fname.endswith('.mm'):
                    module_name = dirName.split(path)[1]
                    if dirName != path:
                        module_name = dirName.split(path + '/')[1]
                    if len(module_name) == 0:
                        module_name = path.split('/')[-1]
                    if '/' in module_name:
                        module_name = module_name.split('/')[0]
    
                    list = []
                    if file_map.has_key(module_name):
                        list = file_map[module_name]
                    full_path = '%s/%s' % (dirName,fname)
                    if full_path not in list:
                        list.append(full_path)
                    file_map[module_name] = list
        return file_map
    

    这里的思路是将指定路径的第一级目录设置为模块名,并将该模块名下的所有文件组织成文件列表建立键值对关系,最终结构类似{"ModuleA":["File1","File2",...], "ModuleB":["File1","File2",...]}

    提取文件引用列表
    #get import list from source_file
    def get_import_files(source_file):
        import_files = []
        for line in fileinput.input(source_file):
            if '#import' in line and '//' not in line:
                file = line.split('#import')[1]
                if '<' in file:
                    file = file.split('<')[1].split('>')[0]
                    if '/' in file:
                        file = file.split('/')[1]
                elif '"' in file:
                    file = file.split('"')[1]
                import_files.append(file)
        return import_files
    

    这里是用关键字匹配的方式提取出指定文件的引用列表,到这一步还不知道哪些文件是跨模块引用的

    分析跨模块引用

    最关键的是这一步,这里采用的是直接遍历的方式来查找跨模块关系,代码较长这里就不贴出了,感兴趣的可以查看Demo工程,最终生成如下格式的数据

    [{"A":[ {"B":[{"B1":"A1"},{"B2":"A1"}, ...] },{"C":[ {"C1":"A1"},{"C2":"A1"},...]}, ...] }, 
    {"B":[ {"C":[{"C1":"B1"}, {"C2":"B1"}, ...] },{"A":[ {"A1":"B1"},{"A2":"B1"}, ...]}, ...]}, ...]
    
    格式化打印

    这一步就是将前面生成的数据格式化输出以便直观查看引用关系

    用法说明

    以下例子基于Demo工程 AnalyseDependencyDemo 进行演示,该工程有4个模块,如下图所示:

    image

    使用该脚本分析跨模块引用关系的结果如下:

    image

    再结合使用 https://github.com/nst/objc_dep 图像化显示引用关系如下图所示:

    image

    可见该工程的引用关系十分混乱,不利于长期维护,因此有必要做一些结构分层。还是以上面的工程为例,我们抽象出一个中间层,所有业务层都向下引用该层,但不允许反向依赖,整理后的结构如下图所示:

    image

    使用该脚本分析跨模块引用关系的结果如下:

    image

    再使用 https://github.com/nst/objc_dep 图像化结果如下:

    image

    整理后的工程引用关系清晰,数据流向单一,有利于工程的可维护性和扩展性,因此十分有必要不定期在工程里检查模块间的引用情况,避免到了后期维护成本过高。

    该脚本还支持指定分析两个模块间的引用情况,参数1 跨模块引用 参数2 的结果如下:

    image

    总结

    这个脚本的原理比较简单,关键是要有这个意识,在版本迭代过程中时刻注意控制工程结构的合理性,不定期使用这个脚本检测跨模块引用情况,等到了后期无法维护的时候再回头重构的成本就会很高。


    欢迎大家关注我们团队的公众号,不定期分享各类技术干货


    image

    相关文章

      网友评论

        本文标题:使用Python脚本分析iOS工程跨模块引用

        本文链接:https://www.haomeiwen.com/subject/xpjdeftx.html