Binaryizing iOS Components to Reduce Build Time
To address the growing build times of the Fire Shopkeeper iOS client caused by nearly 100 components, the team implemented component binaryization using custom CocoaPods plugins, static‑framework packaging, binary storage, and CI integration, dramatically cutting compile duration from forty minutes to around twelve minutes.
The Fire Shopkeeper iOS client has grown to almost 100 components, causing the main project’s build time to increase from a few minutes to about forty minutes and slowing CI pipelines. Reducing compilation time became a pressing need for the development team.
Why Binaryization?
Beyond modular reuse and lighter development, binaryizing components allows pre‑compiled static or dynamic libraries to be linked directly, greatly speeding up integration for both the app and higher‑level components.
Compared with source dependencies, binary dependencies only need linking, not compilation, which reduced the main project’s packaging time from ~40 minutes to as fast as 12 minutes, cutting CI lint, build, and publish stages by several folds.
Because no mature dependency‑switching tool existed, a custom cocoapods‑bin plugin was created for the team.
Binaryization Requirements
Do not affect teams that have not adopted binaryization.
Support source ↔ binary dependency switching at the component level.
Fall back to source when a binary version is unavailable.
Provide a CocoaPods‑like experience for users.
Introduce minimal additional workload.
Macro Handling
Macros used inside components become ineffective after binaryization. Two cases are handled:
Inside methods – a TDFMacro class replaces macros with runtime logic.
Outside methods – code is refactored to eliminate macros, e.g., replacing #if DEBUG … #else … #endif with plain constants.
// TDFMacro.h
@interface TDFMacro : NSObject
+ (BOOL)enterprise;
+ (BOOL)debug;
+ (void)debugExecute:(void(^)(void))debugExecute elseExecute:(void(^)(void))elseExecute;
+ (void)enterpriseExecute:(void(^)(void))enterpriseExecute elseExecute:(void(^)(void))elseExecute;
@end
// TDFMacro.m
@implementation TDFMacro
+ (BOOL)enterprise {
#if ENTERPRISE
return YES;
#else
return NO;
#endif
}
+ (BOOL)debug {
#if DEBUG
return YES;
#else
return NO;
#endif
}
+ (void)debugExecute:(void (^)(void))debugExecute elseExecute:(void (^)(void))elseExecute {
if ([self debug]) { !debugExecute ?: debugExecute(); }
else { !elseExecute ?: elseExecute(); }
}
+ (void)enterpriseExecute:(void (^)(void))enterpriseExecute elseExecute:(void (^)(void))elseExecute {
if ([self enterprise]) { !enterpriseExecute ?: enterpriseExecute(); }
else { !elseExecute ?: elseExecute(); }
}
@endCreating Binary Packages
The first step is to produce a binary archive. The team uses cocoapods‑packager (or its fork cocoapods‑packager‑pro ) to build a static framework:
pod package TDFNavigationBarKit.podspec --exclude-deps --force --no-mangle --spec-sources=http://git.xxx.net/ios/cocoapods-spec.gitAfter encountering resource‑copying issues, the fork adds proper handling of resources and subspecs.
Once the .framework is built, it is zipped:
zip --symlinks -r TDFNavigationBarKit.framework.zip TDFNavigationBarKit.frameworkThe resulting package contains headers, resources, and module maps; soft links must be resolved in the podspec’s source_files and public_header_files fields.
Storing Binary Packages
Two storage options exist: embedding the binary in the component’s Git repository or hosting it on a static file server. The team prefers a static server because it enables API‑driven access, separates source from binary, and avoids inflating repository size.
Switching Dependency Modes
Four strategies were explored:
Single private source, single version – uses environment variables to toggle podspec fields.
Single private source, double version – adds a “‑binary” suffix to version numbers.
Dual private sources, single version – switches the source URL between a source‑only and a binary‑only spec.
Dual private sources, double version – combines the previous two ideas.
The final solution is the dual‑source, single‑version approach, implemented by extending Pod::Resolver to replace specifications with those fetched from the binary source when use_binaries is enabled. If a binary spec cannot be found, the resolver falls back to the source spec and logs a warning.
CI Integration
The binary workflow is fully integrated into GitLab CI. Key stages include component checks, linting, testing, packaging, publishing, and reporting. Environment variables such as BINARY_FIRST=1 prioritize binary dependencies, while scripts automate packaging and publishing of both source and binary pods.
variables:
BINARY_FIRST: 1
DISABLE_NOTIFY: 0
before_script:
- export LANG=en_US.UTF-8
- export LANGUAGE=en_US:en
- export LC_ALL=en_US.UTF-8
- git clone [email protected]:ios/ci-yaml-shell.git
- ci-yaml-shell/before_shell_executor.sh
stages:
- check
- lint
- test
- package
- publish
- report
- cleanup
package_framework:
stage: package
only: [tags]
script:
- ci-yaml-shell/framework_pack_executor.sh
publish_binary_pod:
stage: publish
only: [tags]
script:
- ci-yaml-shell/publish_binary_pod.shConclusion
Component binaryization required significant effort but yielded clear benefits: build times dropped from ~40 minutes to ~12 minutes, CI pipelines became faster, and release cycles shortened. However, for teams without extensive componentization or CI automation, the cost may outweigh the gains.
References
[1] iOS CocoaPods组件平滑二进制化解决方案 https://www.jianshu.com/p/5338bc626eaf [2] iOS CocoaPods组件平滑二进制化解决方案及详细教程二之subspecs篇 https://www.jianshu.com/p/85c97dc9ab83 [3] 组件化-二进制方案 https://www.valiantcat.cn/index.php/2017/05/16/48.html
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
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.