Skip to content

iOS FAQ ​

Q1: Compilation reports no such module 'UjuAdCore' ​

Cause: SWIFT_INCLUDE_PATHS does not correctly point to the xcframework's Headers directory.

Solution:

  1. Confirm that UjuAdCore.xcframework has been added to the project
  2. Configure SWIFT_INCLUDE_PATHS in Build Settings:
    • SWIFT_INCLUDE_PATHS[sdk=iphoneos*] = $(inherited) $(SRCROOT)/Frameworks/UjuAdCore.xcframework/ios-arm64/Headers
    • SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*] = $(inherited) $(SRCROOT)/Frameworks/UjuAdCore.xcframework/ios-arm64_x86_64-simulator/Headers
  3. Disable SWIFT_ENABLE_EXPLICIT_MODULES (set to NO)

See Preparation.

Q2: Linking reports undefined symbol: GRPC.xxx / SwiftProtobuf.xxx ​

Cause: Not using the merged xcframework, or duplicate gRPC-Swift / SwiftProtobuf dependencies.

Solution:

  1. Confirm you are using UjuAdCore.xcframework version 3.4.2 or later (UjuGRPCStatic has been merged)
  2. Remove grpc-swift / swift-protobuf dependencies from Podfile
  3. Check for multiple copies of UjuAdCore (e.g., both xcframework and CocoaPods exist)

Q3: SDK initialization fails with error code 1 (initFailed) ​

Troubleshooting Steps:

  1. Check that appId / appKey (RSA public key) is correct (assigned by the UjuAd platform)
  2. Check that region is selected correctly (.domestic for China / .singapore for overseas)
  3. Enable isDebug = true and check the console logs for the specific failure reason
  4. Confirm that the server domain for the corresponding region is reachable

Q4: Ad loading fails with error code 102 (noFill) ​

Explanation: This is a normal business response, indicating no fill for this request — UjuAd ADX had no bid, or the aggregated budget sources returned no ad. Possible causes:

  • No fill for the current ad placement
  • User frequency cap has been reached
  • No matching ads for the device/region

Since 3.4.3, all no-fill scenarios uniformly call back code=102 / message="无填充" (previously some aggregated budget sources called back code=100 with the third-party raw error attached).

Recommendation: Fall back to another ad source, or retry after an interval.

Q5: When is the splash ad onAdDismissed callback triggered? ​

Callback Timing:

  • User actively skips the splash (clicks the skip button)
  • Splash countdown ends

Recommendation: Enter the main interface in onAdDismissed, not in onAdClosed (the latter only indicates that the ad view was removed).

Q6: How to determine whether a rewarded video user deserves a reward? ​

Determination Logic:

  • onAdRewardArrived() callback → Should grant reward (user watched the complete video)
  • onAdSkippedVideo() callback → Should not grant reward (user skipped the video)
  • onAdPlayComplete() callback → Video playback completed (does not directly equal a reward; use onAdRewardArrived as the authoritative signal)

See Rewarded Video - Reward Logic.

Q7: How to debug SDK internal logs? ​

Method 1 (recommended): Set isDebug = true

swift
let config = UjuAdInitConfig.create(
    appId: "...",
    appKey: "...",
    isDebug: true,  // ★ 开启 SDK 内部日志
    // ...
)

The SDK outputs logs internally via NSLog, which can be viewed in the Xcode console or Console.app. Change to false before release.

Method 2: Use system OSLog

swift
import os.log
let log = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "your.app", category: "UjuAdCore")
os_log("custom log: %{public}@", log: log, message)

Q8: CocoaPods integration reports an unsupported deployment target (The iOS deployment target ... is set to 11.0/13.0) ​

Explanation: All UjuAd components have a unified deployment target of iOS 15.0 since 3.4.3; third-party pods (e.g. CSJ Ads-CN-Beta declares 11.0) will fail under Xcode 27+ for being below the minimum supported version.

Solution: Append a post_install hook at the end of the Podfile to raise the Pods deployment target uniformly:

ruby
post_install do |installer|
  installer.pods_project.targets.each do |t|
    t.build_configurations.each do |c|
      c.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
    end
  end
end

Also make sure platform :ios at the top of the Podfile is not lower than '15.0'. If UjuAdExt is imported, the post_install hook must also set EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64 (see Preparation - CocoaPods Integration).

Q9: Can I compile on macOS? ​

Not supported. UjuAdCore.xcframework is iOS-only; compilation on a macOS host will fail. The SDK only supports iOS platform compilation, not macOS.

Q10: Does the SDK support bitcode? ​

Not supported. Xcode 16+ has deprecated bitcode, and the SDK does not enable bitcode either. Disable ENABLE_BITCODE in Build Settings.

Q11: How to confirm the SDK version? ​

swift
print(UjuAdCore.shared.getVersion())  // "3.4.5"

Or check the UjuAdCore.version.json inside the xcframework.

Q12: Runtime crash EXC_BAD_ACCESS (code=1, address=0x30 / 0x50), stack trace at ManagedAtomic.__allocating_init or NIOPosix.BaseSocketChannel.isActive.getter ​

Root Cause:

UjuAdCore statically links swift-atomics, where ManagedAtomic<Value> is a generic class marked with @_alwaysEmitIntoClient. This marker causes methods like init to be inlined into the caller, but the generic class's type metadata accessor function is provided by the static library on demand.

Under the static library demand-driven linking mode, the linker scans "which .o files are referenced" and only retains referenced .o files. Since @_alwaysEmitIntoClient inlines init into the caller, the generic class's type metadata accessor appears to have no symbol references from the caller's perspective, so it is judged as "unused" by the linker and dead-stripped.

At runtime, ManagedAtomic.__allocating_init calls the type metadata accessor to obtain metadata. After being dead-stripped, this function returns nil, and subsequent access to metadata fields accesses addresses 0x30/0x50 → EXC_BAD_ACCESS.

The crash occurs when the SDK initiates the first network request, manifesting as:

  • Crash immediately after the SDK initiates the first network request
  • Crash occurs on an SDK internal child thread
  • Crash stack top: ManagedAtomic.__allocating_init or BaseSocketChannel.isActive.getter

Solution:

Add -force_load in Target → Build Settings → Other Linker Flags to force retention of all .o in the UjuAdCore static library (select the corresponding slice by SDK condition):

text
OTHER_LDFLAGS[sdk=iphoneos*] = -force_load $(SRCROOT)/Frameworks/UjuAdCore.xcframework/ios-arm64/libUjuAdCore-iphoneos.a
OTHER_LDFLAGS[sdk=iphonesimulator*] = -force_load $(SRCROOT)/Frameworks/UjuAdCore.xcframework/ios-arm64_x86_64-simulator/libUjuAdCore-simulator.a

CocoaPods Integrators: All UjuAd podspecs now automatically inject the -force_load for the corresponding slice via user_target_xcconfig (effective for both device and simulator since 3.4.3); no manual configuration is needed. For manual xcframework integration, configure it as described above.

Q13: Xcode 26 runtime crash on main thread objc_msgSend (SIGSEGV), stack trace contains __NSThreadPerformPerform ​

Root Cause: Xcode 26 enables the Debug Dylib feature by default (ENABLE_DEBUG_DYLIB = YES), which splits app code into <App>.debug.dylib (for hot reload) and the main binary. This split conflicts with UjuAdCore's static library symbol linking: some SDK symbols are assigned to .debug.dylib while the type metadata accessor is assigned to the main binary. Cross-dylib boundary access results in inconsistent metadata → objc_msgSend accesses a released receiver → SIGSEGV.

Solution: Set the following in Target → Build Settings:

text
ENABLE_DEBUG_DYLIB = NO

Impact: Only disables Xcode 26's Swift UI preview hot reload acceleration feature; does not affect SDK functionality or Debug debugging (breakpoints, LLDB, NSLog all work normally). Production Release builds are not affected (Release configuration does not enable Debug Dylib by default).

Q14: How to self-check "whether symbols are correctly linked"? ​

After integration is complete, you can run the following commands in the terminal to verify that key symbols are retained in the app's main binary (replace the path with your actual artifact):

bash
# 1. _AtomicsShims 的 C 桥接函数(若缺失 → -force_load 未生效)
nm -gU <App>.app/<App> | grep "__sa_"
# 期望输出:__sa_retain_n / __sa_release_n

# 2. ManagedAtomic<Bool> 的 type metadata(若缺失 → 仍会崩溃)
nm <App>.app/<App> | grep "7Atomics13ManagedAtomicCySbG"
# 期望包含 MR(metadata accessor)符号

If the above checks fail, go back to Preparation and review the OTHER_LDFLAGS and ENABLE_DEBUG_DYLIB configuration.

Q15: After integrating UjuAdExt, linker reports symbol(s) not found for architecture arm64 with undefined _OBJC_CLASS_$_*** ​

Cause: The third-party DSP SDKs under the Vendor/ directory in the UjuAdExt zip are static libraries (.framework containing .a). Their OC class symbols will be dead-stripped under default linking mode and must be individually -force_load-ed.

Solution:

Append -force_load for each .xcframework static library under the Vendor directory in OTHER_LDFLAGS (replace paths with actual extracted directory structure):

text
OTHER_LDFLAGS[sdk=iphoneos*] = $(inherited) \
  -force_load $(SRCROOT)/Frameworks/UjuAdCore.xcframework/ios-arm64/libUjuAdCore-iphoneos.a \
  -force_load $(SRCROOT)/Frameworks/UjuAdExt.xcframework/ios-arm64/libUjuAdExt-iphoneos.a \
  -force_load $(SRCROOT)/Frameworks/Vendor/Adx24/<SDKName>.xcframework/ios-arm64*/<SDKName>.framework/<SDKName> \
  -force_load $(SRCROOT)/Frameworks/Vendor/Adx28/<SDKName>.xcframework/ios-arm64*/<SDKName>.framework/<SDKName>

See Preparation - Step 5 for details.

Q16: After integrating UjuAdExt, SDK startup log shows Info.plist 反射扫描完成,成功注册 0 个第三方适配器 ​

Cause: UjuAdExt adapters are not auto-registered; you must configure uju_adapter_* keys in Info.plist for the SDK to discover them via reflection.

Solution:

Add the adapter registration keys to Info.plist (UjuAdExt 3.5.10 includes 5 built-in DSP adapters; the adapters activated for you are subject to assignment by the UjuAd platform):

xml
<key>uju_adapter_adx21</key>
<string>Adx21Adapter.Adx21AdapterFactory</string>
<key>uju_adapter_adx24</key>
<string>Adx24Adapter.Adx24AdapterFactory</string>
<key>uju_adapter_adx28</key>
<string>Adx28Adapter.Adx28AdapterFactory</string>
<key>uju_adapter_adx32</key>
<string>Adx32Adapter.Adx32AdapterFactory</string>
<key>uju_adapter_adx33</key>
<string>Adx33Adapter.Adx33AdapterFactory</string>

After configuration, enable isDebug = true and restart the app. The SDK startup log should show Info.plist 反射扫描完成,成功注册 N 个第三方适配器 (N is the number of adapters you configured).

See Preparation - Step 5 for details.

Q17: How to integrate in an Objective-C project? ​

Description: The UjuAd SDK is developed in Swift. Its public APIs include Swift-only features such as struct, Sendable protocol, and Builder pattern, and no standalone Objective-C compatibility layer is provided. Pure Objective-C projects must integrate via Swift mixed compilation + a custom @objc wrapper.

Integration Steps:

  1. Enable Swift mixed compilation: Create a new .swift file in your Xcode project (it can contain just a single line import UjuAdCore). Xcode will prompt to create a Bridging Header — choose Create Bridging Header to enable Swift compilation support.

  2. Write an @objc Wrapper: Since public classes like UjuAdCore and UjuAdObject do not inherit from NSObject, and UjuAdInitConfig is a struct with a static builder, OC cannot call them directly. You need to write NSObject-subclass wrapper classes in a Swift file and expose them to OC with @objc. Example:

swift
import UjuAdCore

@objc(UjuAdBridge)
final class UjuAdBridge: NSObject {

    @objc static func initialize(appId: String,
                                  appKey: String,
                                  isDebug: Bool,
                                  rsaPublicKey: String) {
        let config = UjuAdInitConfig.create(
            appId: appId,
            appKey: appKey,
            isDebug: isDebug,
            rsaPublicKey: rsaPublicKey
        )
        UjuAdCore.shared.initialize(nil, config: config)
    }

    @objc static func start() {
        UjuAdCore.shared.start(nil)
    }

    @objc static func version() -> String {
        return UjuAdCore.shared.getVersion()
    }
}
  1. Call from OC: In your OC code, import <ProductName>-Swift.h and call the wrapper:
objc
#import "YourApp-Swift.h"

[UjuAdBridge initializeWithAppId:@"YOUR_APP_ID"
                           appKey:@"YOUR_APP_KEY"
                          isDebug:YES
                     rsaPublicKey:@"YOUR_RSA_PUBLIC_KEY"];
[UjuAdBridge start];
NSLog(@"SDK version: %@", [UjuAdBridge version]);

Notes:

  • Protocols such as BaseInitListener and ad listeners carry a Sendable constraint and cannot be exposed directly with @objc. Bridge the callbacks inside your wrapper using an @objc protocol or blocks.
  • Enums like UjuAdRegion must be converted to NSInteger in the wrapper to be exposed to OC.
  • The factory methods and listeners of all 5 ad objects (UjuAdObject) should also be wrapped into OC-friendly interfaces.
  • For a complete wrapper design, refer to the Swift bridging implementation in the official ExternalApp Demo.

Q18: After integrating UjuAdExt, linker reports duplicate symbol errors (duplicate OC class / protocol definitions) ​

Cause: UjuAdExt 3.5.5 already bundles all currently supported third-party DSP SDKs (including their .frameworks and .bundle resources) under the Vendor/ subdirectory. If the integrator's project also imports the same third-party SDK via CocoaPods or manual integration, the linker will encounter duplicate OC class, protocol, and category definitions, triggering duplicate symbol errors.

Common conflict sources:

  • The integrator already uses another聚合 SDK (e.g., CSJ GroMore, GDT mediation) that bundles the same DSP SDKs
  • The integrator has an independent business need that directly imports the same SDK
  • Podfile retains stale dependencies from a previous version

Solution:

  1. Remove the integrator's own copy of the same SDK (required):
    • Podfile: delete the corresponding pod '***' dependency and run pod install
    • Manual imports: delete the corresponding .framework and .bundle from Frameworks, Embed Frameworks, and Copy Bundle Resources
    • Build Settings → Search Paths → Framework Search Paths / Header Search Paths: delete entries pointing to the old SDK
  2. Clean Build Settings:
    • OTHER_LDFLAGS: remove -framework *** and -force_load *** entries pointing to the old SDK (keep UjuAdExt's own -force_load UjuAdExt.xcframework)
    • If you previously used -ObjC, keep it (UjuAdExt's own OC categories also need it)
  3. Clean Build Folder: Xcode menu Product → Clean Build Folder (⇧⌘K); delete ~/Library/Developer/Xcode/DerivedData/<project>-* cache directories
  4. Rebuild and verify: nm -gU Pods/UjuAdExt/Vendor/**/*.framework/* | grep <conflicting-class-name> should appear only once, inside UjuAdExt's Vendor

Why not offer a UjuAdExt/Core subspec so integrators can choose?

UjuAdExt's design goal is "integrators only need to import Uju's privacy policy to use all ad sources" — third-party DSP SDKs are uniformly packaged, signed, and version-managed by Uju to avoid version mismatches caused by integrator self-import. If you have a strong need for independent version management, please contact Uju technical support to evaluate a customized solution.

See the "do not re-import" warning in Preparation - Integrate UjuAdExt.

Q19: Uploading to App Store reports "Upload Symbols Failed — The archive did not include a dSYM for the Adx22_SSL.framework / Adx33_Core.framework" ​

Important correction (2026-10-30): an integrator report led us to discover that the Adx22_SSL / Adx33_Core dSYMs shipped in 3.5.4~3.5.9 were stub files constructed from the binary itself — the UUID matches (it is literally the same bytes) and the symbol table allows function-level symbolication, but they contain no DWARF debug info (zero compile units): no line-level symbolication, and they cannot pass App Store Connect's symbol validation. The previous wording of this entry ("upgrade to ≥ 3.5.4 resolves it") was wrong and has been removed. Starting with UjuAdExt 3.5.10 the package ships the DSP's original full dSYM (with DWARF), and packaging now runs a dSYM content audit (UUID match + compile unit count > 0) that blocks the release on failure — stub files can no longer ship.

Background: Adx33_Core in UjuAdExt is distributed as a dynamic library (Adx22_SSL too, before 3.5.8), i.e. a standalone executable image inside the app. When "Upload your app's symbols" is checked, Xcode looks for a dSYM with the matching UUID in the archive's dSYMs folder. These errors do not block the build or the submission itself.

How to resolve:

  1. For 3.5.4~3.5.9: use the retro-shipped full dSYM (recommended, no upgrade needed): the DSP's original dSYMs, renamed to neutral names, are available as a retro-package (UUIDs match the Adx33_Core binaries shipped in all of 3.5.4~3.5.9; device arm64 F54DC8B9-9071-33E3-82EC-BF0BAEDEF09D, 281 compile units; simulator slice covers both architectures, 562):
    • CocoaPods: overwrite Pods/UjuAdExt/Vendor/Adx33/Adx33_Core.dSYMs/ with the Adx33_Core.dSYMs/ directory from the retro-package, then re-archive;
    • Manual integration: copy the two .dSYM bundles into the archive's dSYMs folder (Organizer → right-click the archive → Show in Finder → open the dSYMs folder) and upload again;
  2. Or upgrade to UjuAdExt ≥ 3.5.10: the package ships the full dSYM; CocoaPods includes it in archives automatically, manual integration follows step 1;
  3. Or, for this submission only, skip symbol upload: uncheck "Upload your app's symbols" in the upload dialog. Uploading symbols for third-party frameworks is not a submission requirement; you only lose Apple-side symbolicated reports for that framework. Your app's own dSYM is unaffected;
  4. Check for orphan dSYMs: if you ever copied dSYM files manually per the old version of this entry, check the archive's dSYMs folder for a leftover Adx22_SSL dSYM — Adx22 was removed in 3.5.8, so its UUID no longer matches any binary in the package and it keeps triggering "Invalid dSYM". Run the following inside the archive (.xcarchive) and delete any dSYM whose UUID does not appear on the binary side:
bash
# dSYM-side UUIDs
find . -name "*.dSYM" -exec dwarfdump --uuid {} \;
# binary-side UUIDs (embedded frameworks + main app)
find . -name "*.framework" -exec sh -c 'dwarfdump --uuid "$1/$(basename "$1" .framework)" 2>/dev/null' _ {} \;
find Products/Applications -maxdepth 2 -type f -exec dwarfdump --uuid {} \; 2>/dev/null

Q20: Does the SDK support iOS 27? ​

Yes. The SDK's deployment target is iOS 15.0 and it is forward-compatible with the latest iOS releases (including iOS 26 / iOS 27). It has been built and verified on real devices with Xcode 27 + iOS 27 SDK; device installation, runtime behavior, and App Store distribution are unaffected by the OS version.

Note (simulator only): Starting with Xcode 27, iOS simulator runtimes no longer support Rosetta (arm64 only). Some third-party DSP components bundled in UjuAdExt (Adx28 / Adx32 libraries; UjuAdExt ≤ 3.5.7 also included the Adx22 Adapter, which is no longer packaged as of 3.5.8) do not yet provide usable arm64 simulator slices. As a result:

  • Installing the app on newer simulator runtimes such as iOS 26.5 / iOS 27 fails with "this app needs to be updated to run on this iOS version" (x86_64 builds are no longer accepted by these runtimes);
  • Forcing an arm64 simulator build fails at the link stage (e.g. building for 'iOS-simulator', but linking in object file ... built for 'iOS').

This limitation affects simulator debugging only, not devices or production distribution. For simulator testing, use an older Xcode and runtime pair that still supports Rosetta, or verify on a real device.

Q21: Does the SDK support iOS 27.1 and the iPhone Duo foldable? How are ad sizes handled across fold/unfold? ​

Yes. Starting with UjuAdCore 3.4.6 (paired with UjuAdExt 3.5.6) the SDK is adapted to Apple's iOS 27.1 foldable platform:

  • Size source: the SDK no longer snapshots the screen size once at initialization. It reads the host app's current window scene (UIWindowScene), so bid-request slot sizes and every budget source's template load size follow the window in real time across fold / unfold, Split View and rotation; with multiple windows on the inner display, the window hosting the ad is used.
  • Full-screen ads (splash / interstitial / rewarded): fully Auto Layout and size-class driven. On the outer display (compact width) they fill the window; on the inner display / large screens (regular width) the interstitial card is capped at 420pt and the rewarded page centers its content column with a maximum width. They re-layout with the window during fold / unfold and never overflow the screen.
  • Banner / native ads: the width always follows the container you pass in; both UjuAd ADX templates and budget-source templates re-layout / scale proportionally with the container width. When the rendered size changes afterwards, FeedAdObjectListener.onAdSizeChanged(width:height:) is called (new in 3.4.6, default empty implementation) — list-based hosts should refresh row heights there.
  • Preferred size: UjuAdConfig.adViewSize (in points) explicitly sets the preferred banner / native size; the SDK uses it as the bid slot size and the template load size (width is capped at the host window's current width). When unset, the current window width is used.
  • Build requirement: the SDK itself does not require Xcode 27.1, but the host app must be rebuilt against the iOS 27.1 SDK to get iPhone Duo's edge-to-edge layout and vertical toolbars (determined by the host's build SDK, independent of the ad SDK).
  • Known limitations: the iPhone Duo simulator in Xcode 27.x is arm64-only, so apps containing UjuAdExt cannot be installed on it due to the third-party slice limitation described in Q20 — verify foldable behavior on a real device. Some budget-source template ads (Adx24 / Adx33) let their own SDK decide whether to re-layout after the container widens; their height stays at the first rendered value.

Contact Technical Support ​

If you encounter issues not covered in this document, you can get support via: