在开始分析之前,定义了一些数据结构。
1 | typedef map<string,ObjCMethodDecl*> MethodDeclMap; |
这里我们为每个类定义了一个 InterfaceModel 模型。主要保存着每个类的所包含的方法,以及类中每个方法是否被使用。
注意:我曾经试过将 MethodDeclMap、MethodIsUsedMap 两个合并成一个类似 InterfaceModel 的 MethodModel 模型。但发现编译时会报错。暂时不知道为什么。以后会研究一下。
由于这次我不需要分析协议的方法,所以我会把协议中的方法过滤掉。还要限制一下我们所分析到的 Decl 是否是系统的。
1 | bool isProtocolMethod(ObjCInterfaceDecl *interfaceDecl, ObjCMethodDecl *methodDecl) { |
当我们解析到一个用户的 ObjCInterfaceDecl 时,会构建一个对应的 InterfaceModel 然后将它存入 map 中。1
2
3
4
5
6ObjCInterfaceDecl *interfaceDecl = (ObjCInterfaceDecl *)decl;
string interfaceName = interfaceDecl->getNameAsString();
if (interfaceMap.find(interfaceName) != interfaceMap.end()) {
InterfaceModel interfaceModel = InterfaceModel();
interfaceMap[interfaceName] = interfaceModel;
}
解析到 ObjCMethodDecl 时,会将 decl 存入对应类的模型中。若是第一次存进去,methodIsUsedMap 对应为false。
因为解析时,系统会解析两次。先将工程内全部头文件解析一次,然后在根据语法树进行解析。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18void addMethod(ObjCInterfaceDecl *interfaceDecl, ObjCMethodDecl *methodDecl) {
ObjCInterfaceDecl *decl = interfaceDecl;
while (decl != NULL) {
if (decl->lookupMethod(methodDecl->getSelector(), methodDecl->isInstanceMethod())) {
string interfaceName = decl->getNameAsString();
string selector = methodDecl->getSelector().getAsString();
InterfaceModel *interfaceModel = &interfaceMap[interfaceName];
bool isExist = (interfaceModel->methodIsUsedMap.find(selector) != interfaceModel->methodIsUsedMap.end());
if (!isExist) {
interfaceModel->methodDeclMap[selector] = methodDecl;
interfaceModel->methodIsUsedMap[selector] = false;
}else {
interfaceModel->methodDeclMap[selector] = methodDecl;
}
}
decl = decl->getSuperClass();
}
}
解析 ObjCMessageExpr ,即我们执行调用 [obj method] 时。
和解析到 ObjCMethodDecl 差不多,只不过是将 methodIsUsedMap 对应为方法标记为 true。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19void addExpr(ObjCInterfaceDecl *interfaceDecl, ObjCMessageExpr *objcExpr) {
ObjCMethodDecl *methodDecl = objcExpr->getMethodDecl();
ObjCInterfaceDecl *decl = interfaceDecl;
while (decl != NULL && isUserSourceDecl(decl)) {
if (decl->lookupMethod(methodDecl->getSelector(), methodDecl->isInstanceMethod())) {
string interfaceName = decl->getNameAsString();
string selector = methodDecl->getSelector().getAsString();
InterfaceModel *interfaceModel = &interfaceMap[interfaceName];
bool isExist = (interfaceModel->methodIsUsedMap.find(selector) != interfaceModel->methodIsUsedMap.end());
if (!isExist) {
interfaceModel->methodDeclMap[selector] = methodDecl;
interfaceModel->methodIsUsedMap[selector] = true;
}else {
interfaceModel->methodIsUsedMap[selector] = true;
}
}
decl = decl->getSuperClass();
}
}
最后我们会将 InterfaceMap 中的每个 InterfaceModel 进行遍历。
对每个 InterfaceModel 中的 MethodIsUsedMap 进行遍历,若为 false ,则从 MethodDeclMap 中取出 Decl 进行警告。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17void DiagnosticationResult() {
DiagnosticsEngine &Diagnostics = Instance.getDiagnostics();
for (InterfaceMap::iterator interfaceIter = interfaceMap.begin(); interfaceIter != interfaceMap.end(); ++interfaceIter) {
InterfaceModel *model = &interfaceIter->second;
for (MethodIsUsedMap::iterator methodIter = model->methodIsUsedMap.begin(); methodIter != model->methodIsUsedMap.end(); ++methodIter) {
string selector = methodIter->first;
bool isUsed = methodIter->second;
if (!isUsed) {
ObjCMethodDecl *methodDecl = model->methodDeclMap[selector];
if (methodDecl != NULL) {
int diagID = Diagnostics.getCustomDiagID(DiagnosticsEngine::Warning, "未使用方法 SEL : %0 ");
Diagnostics.Report(methodDecl->getLocStart(), diagID) << methodDecl->getSelector().getAsString();
}
}
}
}
}
此插件的缺点:
仅仅是对非协议方法、以及 [obj method] 形式的调用进行分析。
对于通知、performSelector等调用方法没有进行对应的分析。