iOS相关技术实现

使用Clang插件

2019-06-04  本文已影响0人  flexih

最近在把一个功能模块从一个独立的项目迁入另一个大型项目中,要求这个功能模块只在某一业务的页面上开启。
首先想到的方法是判断一个页面的类是否属于该业务。对于dynamic framework可以通过NSBundle方法判断

+ (NSBundle *)bundleForClass:(Class)aClass;

对于static链接的framework或者library无法使用这种方式。
只能换一种思路,通过正则搜索代码里哪些类继承自UIViewController,但是对于继承UIViewController的子类,这种手工搜索方式就显得捉襟见肘了。
然后想到能不能在编译的时候,去获得UIViewController的子类名称。这时候Clang插件就派上用场了。

编译Clang插件

首先要下载Clang的代码:

git clone -b release_80 https://github.com/llvm-mirror/llvm.git
git clone -b release_80 https://github.com/llvm-mirror/clang.git llvm/tools/clang
git clone -b release_80 https://github.com/llvm-mirror/compiler-rt.git llvm/projects/compiler-rt
git clone -b release_80 https://github.com/llvm-mirror/clang-tools-extra.git llvm/tools/clang/extra
插件

在 llvm/tools/clang/examples/下增加一个文件夹,新建CPlugin.cpp

#include <iostream>
#include <set>
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendPluginRegistry.h"

using namespace clang;
using namespace std;
using namespace llvm;

set<string> classNames;

namespace CPlugin {
    class MyASTVisitor: public RecursiveASTVisitor <MyASTVisitor> {
    private:
        CompilerInstance &instance;
        ASTContext *context;
    public:
        MyASTVisitor(CompilerInstance &inst): instance(inst) {}
        void setContext(ASTContext &context) {
            this->context = &context;
        }
        
        bool VisitDecl(Decl *decl) {
            string filename = instance.getSourceManager().getFilename(decl->getSourceRange().getBegin()).str();
            if(filename.find("/Applications/Xcode.app/") == 0) return true;
            if (isa<ObjCInterfaceDecl>(decl)) {
                ObjCInterfaceDecl *interDecl = (ObjCInterfaceDecl *)decl;
                if (interDecl->getSuperClass()) {
                    string interName = interDecl->getNameAsString();
                    string superClassName = interDecl->getSuperClass()->getNameAsString();
                    if (superClassName.compare("UIViewController") == 0 || classNames.find(superClassName) != classNames.end()) {
                        if (classNames.find(interName) == classNames.end()) {
                            classNames.insert(interName);
                            cout << "-------- ClassName:" << interName << endl;
                        }
                    }
                }
            }
            
            return true;
        }
    };
    
    class MyASTConsumer: public ASTConsumer {
    private:
        MyASTVisitor visitor;
        void HandleTranslationUnit(ASTContext &context) {
            visitor.setContext(context);
            visitor.TraverseDecl(context.getTranslationUnitDecl());
        }
    public:
        MyASTConsumer(CompilerInstance &inst): visitor(MyASTVisitor(inst)) {}
    };
    class MyASTAction: public PluginASTAction {
    public:
        unique_ptr <ASTConsumer> CreateASTConsumer(CompilerInstance & Compiler, StringRef InFile) {
            return unique_ptr <MyASTConsumer> (new MyASTConsumer(Compiler));
        }
        bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string>& args) {
            return true;
        }
    };
}
static clang::FrontendPluginRegistry::Add<CPlugin::MyASTAction> X("CPlugin", "CPlugin desc");

新建CMakeLists.txt

add_llvm_library(CPlugin MODULE CPlugin.cpp PLUGIN_TOOL clang)

if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN))
  target_link_libraries(CPlugin PRIVATE
    clangAST
    clangBasic
    clangFrontend
    clangLex
    LLVMSupport
    )
endif()

修改examples/CMakeLists.txt,在文件的末尾增加add_subdirectory(CPlugin)

编译

需要先安装cmake

brew install cmake

编译方法

mkdir llvm/build
cd llvm/build
cmake -G Xcode ../ -DCMAKE_BUILD_TYPE:STRING=MinSizeRel

等待结束后在llvm/build目录下找到LLVM.xcodeproj,使用Xcode打开后编译clang,libclang和CPlugin三个target。

运行

打开目标project,在Build Settings里Add User-Defined Setting增加

CC=/path/to/llvm/build/Debug/bin/clang
CXX=/path/to/llvm/build/Debug/bin/clang++

在Build Settings里的Other C Flags增加

-Xclang -load -Xclang /path/to/llvm/build/Debug/lib/CPlugin.dylib -Xclang -add-plugin -Xclang CPlugin

如果遇到错误

clang: error: cannot specify -o when generating multiple output files

关掉Build Settings里的Enable Index-While-Building Functionality


收工

经过一系列的操作,就能得到所有UIViewController的子类类名了。
PS:可以优化类名的写操作,直接写到项目的某个文件里,这样就无需在build info里查找了。

上一篇下一篇

猜你喜欢

热点阅读