iOS FAQ
Q1: Compilation reports no such module 'UjuAdCore'
Cause: SWIFT_INCLUDE_PATHS does not correctly point to the xcframework's Headers directory.
Solution:
- Confirm that
UjuAdCore.xcframeworkhas been added to the project - Configure
SWIFT_INCLUDE_PATHSin Build Settings:SWIFT_INCLUDE_PATHS[sdk=iphoneos*] = $(inherited) $(SRCROOT)/Frameworks/UjuAdCore.xcframework/ios-arm64/HeadersSWIFT_INCLUDE_PATHS[sdk=iphonesimulator*] = $(inherited) $(SRCROOT)/Frameworks/UjuAdCore.xcframework/ios-arm64_x86_64-simulator/Headers
- Disable
SWIFT_ENABLE_EXPLICIT_MODULES(set toNO)
See Preparation.
Q2: Linking reports undefined symbol: GRPC.xxx / SwiftProtobuf.xxx
Cause: Not using the merged xcframework, or duplicate gRPC-Swift / SwiftProtobuf dependencies.
Solution:
- Confirm you are using
UjuAdCore.xcframeworkversion 3.4.2 or later (UjuGRPCStatic has been merged) - Remove
grpc-swift/swift-protobufdependencies from Podfile - Check for multiple copies of UjuAdCore (e.g., both xcframework and CocoaPods exist)
Q3: SDK initialization fails with error code 1 (initFailed)
Troubleshooting Steps:
- Check that
appId/appKey(RSA public key) is correct (assigned by the UjuAd platform) - Check that
regionis selected correctly (.domesticfor China /.singaporefor overseas) - Enable
isDebug = trueand check the console logs for the specific failure reason - 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; useonAdRewardArrivedas the authoritative signal)
See Rewarded Video - Reward Logic.
Q7: How to debug SDK internal logs?
Method 1 (recommended): Set isDebug = true
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
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:
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?
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/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_initorBaseSocketChannel.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):
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.aCocoaPods 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:
ENABLE_DEBUG_DYLIB = NOImpact: 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):
# 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):
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):
<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:
Enable Swift mixed compilation: Create a new
.swiftfile in your Xcode project (it can contain just a single lineimport UjuAdCore). Xcode will prompt to create a Bridging Header — choose Create Bridging Header to enable Swift compilation support.Write an
@objcWrapper: Since public classes likeUjuAdCoreandUjuAdObjectdo not inherit fromNSObject, andUjuAdInitConfigis astructwith a static builder, OC cannot call them directly. You need to writeNSObject-subclass wrapper classes in a Swift file and expose them to OC with@objc. Example:
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()
}
}- Call from OC: In your OC code, import
<ProductName>-Swift.hand call the wrapper:
#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
BaseInitListenerand ad listeners carry aSendableconstraint and cannot be exposed directly with@objc. Bridge the callbacks inside your wrapper using an@objcprotocol or blocks. - Enums like
UjuAdRegionmust be converted toNSIntegerin 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:
- Email: marco@ujuad.com
- Website: https://www.ujuad.com
