程序员ios专题移动开发技术前沿

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

2018-06-13  本文已影响154人  hi_xgb
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
上一篇下一篇

猜你喜欢

热点阅读