Advertisement

Technical Report: Integrating Native AdMob in Cocos Creator iOS Projects

2026-03-10

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.
  • 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 = YES caused 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 6 crash 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:

    1. 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.
    2. 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:
    1. Podfile: Prohibit all Pod targets from embedding Swift libraries.
    2. CMakeLists.txt:
      • Set ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO.
      • Critical Fix: Append /usr/lib/swift to LD_RUNPATH_SEARCH_PATHS to instruct dyld to search the system directory.

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 MarketplaceKit for 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 AppDelegate lifecycle (potentially before the Cocos engine is ready), creating a race condition with system framework initialization.

3. Solution: Upgrade + Delay + Configure

  • Steps:
    1. Upgrade: Force update SDK to 13.0.0+.
    2. Disable Auto-Init: Configure Info.plist to stop automatic startup.
    3. Delayed Init: Move initialization code from AppDelegate to ViewController with 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.

Advertisement