How to Perform Fine‑Grained Dependency Analysis for iOS Modules with LLVM and Clang Plugins
This article explains why modern iOS apps need modular dependency analysis, compares common approaches such as CocoaPods, header scanning, and symbol tools, and then details a source‑level LLVM‑based solution that uses Clang plugins to generate precise method‑level dependency metadata for each bundle.
Background and Challenges
As iOS applications move from monolithic builds to modular component bundles, developers must ensure that many independent bundles can be integrated reliably, understand the impact of updating or removing a bundle, and identify bundles that can be eliminated during optimization. Accurate dependency relationships between bundles are required to address these challenges.
Definition of Dependency Analysis
Dependency analysis extracts and represents the relationships among subsystems of a complex system in a programmatically consumable form.
Common Dependency Analysis Approaches
CocoaPods package analysis : Parses Podfile.lock and .podspec files, which explicitly list bundle dependencies. This method is simple and industry‑standard but provides only bundle‑level granularity and must be run before compilation.
Header‑file scanning : Scans #include and #import directives to infer file‑level dependencies. It is easy to implement but can produce false or circular dependencies and also runs before compilation.
Symbol analysis with nm / otool : Reads symbol tables from compiled binaries to build a fine‑grained (symbol‑level) dependency graph. It works after compilation but requires unstripped binaries and may miss symbols in optimized builds.
LLVM‑Based Fine‑Grained Dependency Analysis
The LLVM project provides a modular compiler infrastructure with an intermediate representation (IR) that is language‑agnostic. By instrumenting the IR generation phase, a plugin can record precise metadata such as function name, source file, line range, and source hash, enabling method‑level dependency graphs.
Typical compilation pipeline:
Preprocessor : Handles macro expansion, conditional compilation, and file inclusion.
Lexer : Tokenizes the preprocessed source.
Parser (AST generation) : Builds an abstract syntax tree (AST) from tokens.
IR generation (CodeGen) : Traverses the AST and emits LLVM IR.
Optimization (Opt) : Applies target‑independent and target‑specific optimizations (e.g., Bitcode optimization).
Code generation : Converts IR to assembly, object files, and finally links them into an executable.
Preparing the Clang Toolchain
To develop a Clang plugin you need a custom LLVM/Clang build. The following shell script clones the LLVM monorepo (release_80 branch) and builds it with Ninja:
#!/bin/sh
cd /opt
sudo mkdir llvm
pushd llvm && \
git clone -b release_80 [email protected]:llvm-mirror/llvm.git llvm && \
git clone -b release_80 [email protected]:llvm-mirror/clang.git llvm/tools/clang && \
git clone -b release_80 [email protected]:llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra && \
git clone -b release_80 [email protected]:llvm-mirror/compiler-rt.git llvm/projects/compiler-rt && \
popd && \
sudo mkdir -v llvm_build && \
pushd llvm_build && \
cmake -DCMAKE_INSTALL_PREFIX=/opt/llvm_release \
-DLLVM_TARGETS_TO_BUILD="X86;ARM;Mips;AArch64;WebAssembly" \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_ENABLE_FFI=ON \
-DLLVM_ENABLE_RTTI=ON \
-DLLVM_BUILD_TESTS=OFF \
-DLLVM_INCLUDE_TESTS=OFF \
-Wno-dev -G Ninja ../llvm && \
ninja && ninja install && popdIf you prefer a pre‑built toolchain, binaries are available at:
http://releases.llvm.org/
Writing a Clang Plugin
A Clang plugin is a dynamic library loaded by the compiler. The entry point is a FrontendAction that creates an ASTConsumer. The consumer receives the AST and can traverse it using RecursiveASTVisitor. The relevant class hierarchy includes: clang::RecursiveASTVisitor – base class for AST traversal. clang::PluginASTAction – abstract base for plugin front‑end actions. clang::ASTConsumer – abstract base for reading the AST. clang::ObjCInterfaceDecl, clang::ObjCMethodDecl, clang::ObjCMessageExpr – record Objective‑C declarations and message sends.
Example plugin that prints every C++ class name:
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Tooling/Tooling.h"
using namespace clang;
class FindNamedClassVisitor : public RecursiveASTVisitor<FindNamedClassVisitor> {
public:
explicit FindNamedClassVisitor(ASTContext *Context) : Context(Context) {}
bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) {
llvm::outs() << "Found class: " << Declaration->getNameAsString() << "
";
return true;
}
private:
ASTContext *Context;
};
class FindNamedClassConsumer : public ASTConsumer {
public:
explicit FindNamedClassConsumer(ASTContext *Context) : Visitor(Context) {}
void HandleTranslationUnit(ASTContext &Context) override {
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
}
private:
FindNamedClassVisitor Visitor;
};
class FindNamedClassAction : public ASTFrontendAction {
public:
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, llvm::StringRef) override {
return std::make_unique<FindNamedClassConsumer>(&CI.getASTContext());
}
};Compile the plugin with the custom Clang and load it via compiler flags (added to Xcode “Other C/C++ Flags”):
-Xclang -load -Xclang /opt/llvm_release/plugins/libMyPlugin.dylib -Xclang -add-plugin -Xclang MyPluginGenerating Dependency Metadata
When the instrumented plugin runs during the IR generation phase, it can emit JSON records for each method. Example record:
{
"+[GTMBase64 decodeBytes:length:]": {
"call": ["+[GTMBase64 baseDecode:length:charset:requirePadding:]"] ,
"class": "GTMBase64",
"filename": "/Sources/Internal/Encode/GTMBase64.m",
"range": "11401-11553",
"sourceCode": "{return [self baseDecode:bytes length:length charset:kBase64DecodeChars requirePadding:YES];}"
}
}The keys represent the method signature, called methods, owning class, source file, line range, and the actual source snippet. Aggregating such records across all bundles yields a fine‑grained dependency graph that supports impact analysis and safe bundle removal.
Conclusion
The article reviews traditional iOS dependency‑analysis techniques, highlights their limitations, and presents an LLVM‑based solution that captures method‑level relationships through a custom Clang plugin. This approach provides reliable metadata for architecture optimization and future modular evolution.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Amap Tech
Official Amap technology account showcasing all of Amap's technical innovations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
