How We Cut iOS Build Times by 66%: Real-World Xcode Compilation Optimizations
This article details a comprehensive set of practical techniques—ranging from Xcode project configuration tweaks and CocoaPods packaging to CCache, distcc, hardware tricks, PCH usage, and deep Clang/LLVM analysis—that together reduced a massive iOS build from over 27 minutes to under 10 minutes.
Preface
Time feels like a pig farm; over the years both my weight and the WeChat codebase have grown. In 2014, compiling the WeChat project on a laptop took about ten minutes; now on a 2017 27‑inch iMac it nears half an hour, and occasional full recompiles appear mysteriously. The low compilation efficiency hurt developer morale, so I requested and received approval to optimize the build process.
Existing Solutions
Before acting, I surveyed current approaches and identified several optimization points:
1. Optimize Project Configuration
1) Change Debug Information Format to DWARF
Debug builds do not need full symbol tables; ensure sub‑projects (especially third‑party libraries) use the correct setting.
2) Set Build Active Architecture Only to Yes
Debug builds do not need to compile all architectures; verify sub‑projects are configured accordingly.
3) Optimize Header Search Paths
Avoid recursive Header Search Paths that cause excessive -I arguments and slow preprocessing. The same applies to Framework Search Paths.
2. Use CocoaPods to Manage Third‑Party Libraries
Package any pod into a static library with cocoapods-packager to avoid repeated compilation, though debugging source code becomes harder.
3. CCache
CCache caches intermediate compilation artifacts without major project changes. It solved an Xcode 9 bug that triggered full recompiles even when sources were unchanged, but Xcode 10 fixed that issue, reducing CCache’s relevance.
4. distcc
distcc distributes compilation tasks across multiple machines, returning object files for final linking.
5. Hardware Solutions
Place the Derived Data directory on a RAM‑disk or upgrade to the latest iMac Pro.
Practical Process
1. Optimize Compilation Options
1) Refine Header Search Paths
Removing recursive paths saved about 20 seconds overall.
2) Disable Enable Index‑While‑Building Functionality
This Xcode 9 feature builds code indexes during compilation, slowing it down. Turning it off shaved roughly 80 seconds.
2. Optimize "kinda" Framework
The cross‑platform C++ payment framework "kinda" compiled extremely slowly (≈30 s per source file) and produced large binaries. Analysis of the LinkMap revealed two main issues:
Excessive code generated from protobuf files.
Heavy template usage in a base class/macros.
For the first issue, set protobuf option optimize_for=CODE_SIZE to generate slimmer code (my own tool produces even fewer lines).
For the second, replace template‑based polymorphism with runtime polymorphism via virtual base classes and use hyper_function instead of std::function. Example before and after:
template <typename Request, typename Response>
class BaseCgi {
public:
BaseCgi(Request request, std::function<void(Response &)> &callback) {
_request = request;
_callback = callback;
}
// ...
Request _request;
std::function<void(Response &)> _callback;
};
class CgiA : public BaseCgi<RequestA, ResponseA> { /* ... */ }; class BaseRequest { public: virtual void toData(std::vector<uint8_t> &outData) = 0; };
class BaseResponse { public: virtual void fromData(std::vector<uint8_t> &outData) = 0; };
class BaseCgi {
public:
template <typename Request, typename Response>
BaseCgi(Request &request, hyper_function<void(Response &)> callback) {
_request = new Request(request);
_response = new Response;
_callback = callback;
}
// ...
BaseRequest *_request;
BaseResponse *_response;
hyper_function<void(BaseResponse &)> _callback;
};
class CgiA : public BaseCgi { /* ... */ };After these changes, compilation speed improved by 70 seconds and the binary size shrank by 60 %.
3. Use Precompiled Header (PCH)
PCH files contain common macros and headers, speeding up subsequent compilations. In Xcode, set Prefix Header and enable Precompile Prefix Header.
WeChat’s build time dropped by roughly 280 seconds after adding PCH.
Ultimate Optimization
Overall, the build time fell from 1,626.4 s to 1,182.8 s , a gain of about 450 seconds, yet still around 20 minutes. Further gains require compiler‑level improvements.
1. Compilation Theory
Compilers translate high‑level code to low‑level code and consist of three parts: Frontend (parses source, builds AST), Optimizer (architecture‑agnostic optimizations), and Backend (generates machine code). LLVM provides a modular framework with an intermediate representation (LLVM IR). In Xcode, Clang serves as the frontend and LLVM as the backend.
Clang’s compilation phases:
➜ clang -ccc-print-phases main.m
0: input, "main.m", objective-c
1: preprocessor, {0}, objective-c-cpp-output
2: compiler, {1}, ir
3: backend, {2}, assembler
4: assembler, {3}, object
5: linker, {4}, image
6: bind-arch, "x86_64", {5}, imagePreprocessing
Handles header inclusion, macro expansion, directives, and comment removal.
Compilation
Performs lexical analysis, syntax analysis (AST generation), static analysis, and IR generation.
Assembly Generation
LLVM optimizes IR according to the selected optimization level (e.g., -O0 for Debug, -Os for Release) and emits platform‑specific assembly.
Object File Generation
The assembler converts assembly to object files (.o).
Linking
The linker combines object files into an executable.
2. Analyzing Bottlenecks
Using Clang’s -ftime-trace option (added by Aras Pranckevičius) we generated Chrome‑compatible JSON traces showing that the Frontend consumed 5,307.9 ms of the total 8,423.8 ms, with header processing dominating.
Further analysis showed that the top‑10 header files accounted for the majority of preprocessing time; optimizing them (reducing recursive includes, using forward declarations) saved additional seconds.
3. Solving the Bottleneck
Replace unnecessary #include directives with forward declarations wherever possible. This required extensive manual work (five days) but yielded an extra 80‑second reduction.
To automate the process, we built a Clang‑based tool that traverses the AST, identifies symbols, and decides whether an #include is truly needed or a forward declaration suffices. The tool builds on Google’s open‑source include‑what‑you‑use (IWYU) and adds Objective‑C support and result pruning.
After applying the tool, the total build time dropped to 710 s . Further profiling identified WCDB header processing as a remaining hotspot; solutions include moving WCDB includes to PCH or isolating them in category headers.
Ultimately, the build time fell below 540 seconds—about one‑third of the original duration.
Optimization Summary
A) Optimize header search paths.
B) Disable Enable Index‑While‑Building Functionality.
C) Reduce protobuf/template bloat.
D) Use precompiled headers (PCH).
E) Employ tools to prune unnecessary includes and avoid embedding the C++ standard library in headers.
Future Outlook
We look forward to the company’s distributed ObjC compilation system and further modularization of business code, as demonstrated by the "kinda", mini‑program, and Mars modules.
References
How to Speed Up iOS Project Build Times by 5×
Deep Dive into iOS Compilation with Clang/LLVM
Clang AST Overview
time‑trace: Timeline/Flame Chart Profiler for Clang
Introduction to the Clang AST
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.
WeChat Client Technology Team
Official account of the WeChat mobile client development team, sharing development experience, cutting‑edge tech, and little‑known stories across Android, iOS, macOS, Windows Phone, and Windows.
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.
