Technical Report: Integrating Native AdMob in Cocos Creator iOS Projects
Integrating native iOS SDKs into a cross-platform engine like Cocos Creator often involves navigating a complex landscape of build systems and language interoperability. This report details the technical challenges and solutions encountered while integrating the Google Mobile Ads SDK (AdMob) into a Cocos Creator 3.8.x project via the native iOS engineering path.
1. Project Context & Architecture
- Project Type: Cocos Creator Cross-Platform Game (iOS Native Target)
- Integration Strategy: Native Integration
- Instead of using official or third-party Cocos Store plugins, we integrated the SDK directly into the generated iOS project (
native/engine/ios) using CocoaPods. - This approach, while requiring more configuration, grants absolute control over SDK versions and initialization logic, which is crucial for resolving compatibility issues.
- Instead of using official or third-party Cocos Store plugins, we integrated the SDK directly into the generated iOS project (
- Core Tech Stack:
- Engine: Cocos Creator 3.8.x (C++/TypeScript)
- Build System: CMake (generates Xcode project) + CocoaPods (dependency management)
- Languages: Objective-C (Native Bridge) + Swift (Third-party SDKs)
- Key Dependency: Google Mobile Ads SDK (AdMob)
- Challenge: AdMob is a binary library containing Swift code, while the Cocos main project defaults to a C++/Objective-C environment. This hybrid environment is the root cause of the build issues described below.
2. Core Issues & Solutions
We encountered two major blocking categories: Build-time Swift Standard Library Conflicts and Runtime AdMob SDK Compatibility Crashes.
Issue 1: Swift Standard Library Embedding & RPATH Conflicts
1. Symptoms
We faced mutually exclusive errors during build or runtime:
- Build Error: Enabling
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YEScaused Xcode to fail with:product type 'com.apple.product-type.library.static' is not a wrapper type - Crash A (Missing Library): Disabling embedding caused an immediate crash on launch:
dyld: Library not loaded: @rpath/libswiftCore.dylib - Crash B (Version Conflict): Forcing embedding resulted in a
Signal 6crash on newer iOS versions:This copy of libswiftCore.dylib requires an OS version prior to 12.2.0
2. Root Cause Analysis
-
ABI Stability: As per Apple's Release Notes, iOS 12.2 and later include the Swift runtime built-in.
App Thinning: "Swift apps no longer include dynamically linked libraries for the Swift standard library... in build variants for devices running iOS 12.2..."
-
The Conflict:
- Crash B: If the app bundles an older version of the Swift libraries (automatically added by Xcode for backward compatibility), it conflicts with the system's built-in Swift runtime on iOS 12.2+ devices.
- Crash A: If we simply disable embedding without further action, the app's default RPATH (
@executable_path/Frameworks) fails to locate the system-provided Swift libraries.
3. Solution: Disable Embedding & Fix Search Paths
The strategy is "Don't bring the library, but show the way."
- Implementation:
- Podfile: Prohibit all Pod targets from embedding Swift libraries.
- CMakeLists.txt:
- Set
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO. - Critical Fix: Append
/usr/lib/swifttoLD_RUNPATH_SEARCH_PATHSto instructdyldto search the system directory.
- Set
Code Implementation:
1. Podfile (native/engine/ios/Podfile)
post_install do |installer|
installer.pods_project.targets.each do |target|
# Force disable Swift embedding for Pods
target.build_configurations.each do |config|
config.build_settings['ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES'] = 'NO'
end
# Remove any existing copy script phases
target.build_phases.each do |phase|
if phase.respond_to?(:name) && phase.name == "Copy Swift Standard Libraries"
phase.remove_from_project
end
end
end
# Fix Main Target RPATH
user_project = installer.aggregate_targets.map(&:user_project).compact.first
app_target = user_project.targets.find { |t| t.name == 'iTallyCounter' } # Replace with your target name
app_target.build_configurations.each do |config|
config.build_settings['ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES'] = 'NO'
# Add system Swift library path
ld_paths = config.build_settings['LD_RUNPATH_SEARCH_PATHS']
paths_to_add = ['@executable_path/Frameworks', '/usr/lib/swift']
if ld_paths.nil?
config.build_settings['LD_RUNPATH_SEARCH_PATHS'] = ['$(inherited)'] + paths_to_add
elsif ld_paths.is_a?(Array)
config.build_settings['LD_RUNPATH_SEARCH_PATHS'] = ld_paths + paths_to_add
end
end
user_project.save
end
2. CMakeLists.txt (native/engine/ios/CMakeLists.txt)
set_target_properties(${EXECUTABLE_NAME} PROPERTIES
# ...
# Disable embedding
XCODE_ATTRIBUTE_ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES "NO"
# Fix RPATH to point to system libraries
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks /usr/lib/swift"
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
# ...
)
Issue 2: AdMob Initialization Crash (SIGABRT)
1. Symptoms
On iOS 17.4+ devices, the app crashes on launch with SIGABRT. The stack trace points to:
GADMarketplaceKitSignals.appDistributor()
2. Root Cause Analysis
- MarketplaceKit Compatibility: iOS 17.4 introduced
MarketplaceKitfor EU third-party app stores. Older AdMob SDKs (< 11.x) crash when attempting to retrieve distribution signals via this new API due to unhandled edge cases in permissions or return values. - Initialization Race Condition: AdMob's default "Auto Measurement" starts initialization extremely early in the
AppDelegatelifecycle (potentially before the Cocos engine is ready), creating a race condition with system framework initialization.
3. Solution: Upgrade + Delay + Configure
- Steps:
- Upgrade: Force update SDK to 13.0.0+.
- Disable Auto-Init: Configure
Info.plistto stop automatic startup. - Delayed Init: Move initialization code from
AppDelegatetoViewControllerwith a 1-second delay.
Code Implementation:
1. Info.plist (native/engine/ios/Info.plist)
<!-- Disable AdMob Auto Init -->
<key>GADDelayAppMeasurementInit</key>
<true/>
<!-- Disable UMP Auto Init -->
<key>UMPDelayAppMeasurementInit</key>
<true/>
2. ViewController.mm (Delayed Initialization)
// Remove any startWithCompletionHandler calls from AppDelegate.mm
// Move to ViewController.mm
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Delay 1s to avoid launch spikes and race conditions
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"[AdMob] Delayed initialization start");
[[GADMobileAds sharedInstance] startWithCompletionHandler:nil];
// Load ads only after initialization
[self setupAdBanner];
});
}
By systematically resolving the build system conflicts and managing the SDK lifecycle, we achieved a stable integration that passes Apple's validation and runs reliably on the latest iOS versions.