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 that ADX had no bid this time. Possible causes:

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

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 SDK does not support iOS 12

Solution: UjuAdCore requires iOS 13.0 minimum. Set the following in your Podfile:

ruby
platform :ios, '13.0'

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.2"

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/0x50EXC_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 SettingsOther 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: CocoaPods automatically sets OTHER_LDFLAGS = -ObjC, which provides dead-strip protection for Objective-C classes but does not work for Swift generic class type metadata accessors. If using CocoaPods static library integration, you still need to manually add -force_load. XCFramework integration is recommended.

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.4.2 includes 2 built-in DSP adapters):

xml
<key>uju_adapter_adx24</key>
<string>Adx24Adapter.Adx24AdapterFactory</string>
<key>uju_adapter_adx28</key>
<string>Adx28Adapter.Adx28AdapterFactory</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.

Contact Technical Support

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