ToBid iOS 接入指南
本文档指导开发者在 iOS 端将优聚智汇 SDK 以**自定义广告平台(Custom Adapter)**形式接入 ToBid(Sigmob WindMill)聚合平台。
阅读前置
请先阅读 ToBid 自定义接入概述,了解反向集成模式、工作原理、后台配置与常见问题。本文仅介绍 iOS 端的依赖引入、Build Settings 配置、SDK 初始化与广告加载。
版本号说明
| 组件 | 当前版本 | 说明 |
|---|---|---|
| UjuRevToBidAdapter | 5.7.4 | 优聚 ToBid 反向聚合适配器,版本号与 ToBid SDK 版本对齐 |
| UjuAdCore | ~> 3.4.2 | 优聚核心 SDK,由 UjuRevToBidAdapter 强依赖 |
| UjuAdExt | ~> 3.4.2 | 优聚智汇预算扩展包,扩展广告填充来源,必选(优聚 Adx 永远存在,初始化顺序第一顺位) |
| ToBid-iOS(WindMillSDK) | 5.7.4 | 由集成方自行引入,版本必须与 UjuRevToBidAdapter 一致 |
版本对齐要求
UjuRevToBidAdapter 的版本号即对应适配的 ToBid SDK 版本号。集成时必须保证:
pod 'UjuRevToBidAdapter', '5.7.4'pod 'ToBid-iOS', '5.7.4'
两者版本号必须完全一致,否则可能因 ToBid SDK 协议变更导致适配器失效。
开发环境要求
| 项目 | 要求 |
|---|---|
| iOS 部署目标 | iOS 13.0 及以上 |
| Swift 版本 | Swift 5.9 及以上 |
| Xcode 版本 | Xcode 15 及以上(推荐 Xcode 26+) |
| 架构 | arm64(真机)/ arm64 + x86_64(模拟器) |
| 依赖工具 | Xcode / CocoaPods |
SDK 组件关系
ToBid iOS 反向集成场景下,集成方 App 与 SDK 的依赖关系如下:
┌──────────────────────────────────────────────┐
│ 集成方 App │
├──────────────────────────────────────────────┤
│ ToBid-iOS / WindMillSDK(集成方自行引入) │
│ ↓ 运行时通过 ObjC 反射发现适配器 │
│ UjuRevToBidAdapter.xcframework(必选) │
│ ↓ 编译期依赖 │
│ UjuAdExt.xcframework(必选) │
│ └── 内部 DSP 适配器(Vendor 已打包) │
│ ↓ 依赖 │
│ UjuAdCore.xcframework(必选) │
│ ├── 优聚 Adx 竞价引擎 │
│ ├── 配置服务 / 埋点上报 │
│ └── 广告加载 / 渲染 / 展示 │
│ (gRPC-Swift / SwiftProtobuf / SwiftNIO │
│ 已静态链接,无需引入) │
└──────────────────────────────────────────────┘不支持 Banner
UjuRevToBidAdapter 当前支持 4 种广告类型:开屏 / 激励视频 / 插屏 / 原生。Banner 适配器待 UjuAdCore Banner 能力就绪后提供。
集成方式
提供两种集成方式,UjuRevToBidAdapter 为静态库类型(.a + Headers),编译期依赖 UjuAdCore 与 WindMillSDK 的 Headers,运行期由集成方链接完整的 ToBid SDK。
| 方式 | 适用场景 | 推荐度 |
|---|---|---|
| CocoaPods | 使用 CocoaPods 管理依赖的项目 | ★★★ |
| XCFramework | 不使用 CocoaPods 的项目 | ★★ |
方式一:CocoaPods 集成(推荐)
步骤 1:添加私有 spec repo
优聚智汇 SDK 通过私有 CocoaPods 源分发,首次接入需添加 spec repo:
pod repo add UjuAdSpecs https://gitee.com/ujuad/iossdk.git步骤 2:创建 Podfile
在项目根目录创建 Podfile:
source 'https://gitee.com/ujuad/iossdk.git'
source 'https://cdn.cocoapods.org/'
platform :ios, '13.0'
use_frameworks!
target 'YourApp' do
# 优聚核心 SDK(必选,UjuRevToBidAdapter 强依赖)
pod 'UjuAdCore', '~> 3.4.2'
# 优聚智汇预算扩展包(必选,扩展广告填充来源,优聚 Adx 永远存在)
pod 'UjuAdExt', '~> 3.4.2'
# 优聚 ToBid 反向适配器(版本对齐 ToBid SDK)
pod 'UjuRevToBidAdapter', '5.7.4'
# ToBid 聚合 SDK(集成方自行引入,版本必须与 UjuRevToBidAdapter 一致)
pod 'ToBid-iOS', '5.7.4'
endsource 说明
source 'https://gitee.com/ujuad/iossdk.git'必须在 Podfile 顶部声明,用于拉取优聚私有 podspecsource 'https://cdn.cocoapods.org/'用于拉取 ToBid-iOS 等公共依赖UjuRevToBidAdapter与ToBid-iOS版本号必须完全一致
步骤 3:安装依赖
pod install
open YourApp.xcworkspacepodspec 自动配置 Build Settings
CocoaPods 集成模式下,podspec 会自动配置 SWIFT_INCLUDE_PATHS、OTHER_LDFLAGS(含 -force_load、-ObjC)、ENABLE_DEBUG_DYLIB 等 Build Settings,集成方无需手动设置。
步骤 4:验证集成
import UjuAdCore
import UjuRevToBidAdapter // 适配器内部类型,通常无需直接 import
import WindMillSDK // ToBid SDK,由集成方引入
print("UjuAdCore 版本: \(UjuAdCore.shared.getVersion())")
// 输出: UjuAdCore 版本: 3.4.2编译运行,确认无 no such module / undefined symbol 错误即集成成功。
方式二:XCFramework 集成
步骤 1:获取 xcframework
从优聚智汇 Gitee Release 下载以下四个 zip 包:
| 包名 | 下载地址 |
|---|---|
UjuAdCore-3.4.2.xcframework.zip | Gitee Release UjuAdCore-3.4.2 |
UjuAdExt-3.4.2.xcframework.zip | Gitee Release UjuAdExt-3.4.2 |
UjuRevToBidAdapter-5.7.4.xcframework.zip | Gitee Release UjuRevToBidAdapter-5.7.4 |
| ToBid-iOS(WindMillSDK) | 从 ToBid 官方 或 CocoaPods 获取 |
解压 UjuRevToBidAdapter-5.7.4.xcframework.zip 后产物结构如下:
UjuRevToBidAdapter.xcframework/
├── Info.plist # xcframework 元信息
├── ios-arm64/ # 真机 slice
│ ├── libUjuRevToBidAdapter-iphoneos.a # 静态库
│ └── Headers/ # UjuRevToBidAdapter.swiftmodule
├── ios-arm64_x86_64-simulator/ # 模拟器 slice
│ ├── libUjuRevToBidAdapter-simulator.a
│ └── Headers/
│ └── UjuRevToBidAdapter.swiftmodule/
└── UjuRevToBidAdapter.version.json # 版本元信息步骤 2:添加到 Xcode 项目
- 将
UjuAdCore.xcframework、UjuAdExt.xcframework(含其内部Vendor/目录下的第三方 DSP xcframework,按 zip 解压后的目录结构原样保留)、UjuRevToBidAdapter.xcframework、ToBid SDK 的 4 个 xcframework(WindMillSDK.xcframework/WindFoundation.xcframework/WindSDK.xcframework/NexaX.xcframework)拖入项目Frameworks/目录 - 在 Xcode 中选择项目 → Target → General → Frameworks, Libraries, and Embedded Content,确认所有 xcframework 的 Embed 选项为 Do Not Embed(均为静态库)
步骤 3:配置 Build Settings
在 Target → Build Settings 中追加以下配置(在 UjuAdCore 已有配置的基础上追加 UjuAdExt 与 UjuRevToBidAdapter):
// SWIFT_INCLUDE_PATHS 追加 UjuAdExt + UjuRevToBidAdapter 的 Headers 目录
SWIFT_INCLUDE_PATHS[sdk=iphoneos*] = $(inherited) \
$(SRCROOT)/Frameworks/UjuAdCore.xcframework/ios-arm64/Headers \
$(SRCROOT)/Frameworks/UjuAdExt.xcframework/ios-arm64/Headers \
$(SRCROOT)/Frameworks/UjuRevToBidAdapter.xcframework/ios-arm64/Headers
SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*] = $(inherited) \
$(SRCROOT)/Frameworks/UjuAdCore.xcframework/ios-arm64_x86_64-simulator/Headers \
$(SRCROOT)/Frameworks/UjuAdExt.xcframework/ios-arm64_x86_64-simulator/Headers \
$(SRCROOT)/Frameworks/UjuRevToBidAdapter.xcframework/ios-arm64_x86_64-simulator/Headers
// OTHER_LDFLAGS 追加 UjuAdExt + UjuRevToBidAdapter 的 -force_load,以及 -ObjC
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/UjuRevToBidAdapter.xcframework/ios-arm64/libUjuRevToBidAdapter-iphoneos.a \
-ObjC
OTHER_LDFLAGS[sdk=iphonesimulator*] = $(inherited) \
-force_load $(SRCROOT)/Frameworks/UjuAdCore.xcframework/ios-arm64_x86_64-simulator/libUjuAdCore-simulator.a \
-force_load $(SRCROOT)/Frameworks/UjuAdExt.xcframework/ios-arm64_x86_64-simulator/libUjuAdExt-simulator.a \
-force_load $(SRCROOT)/Frameworks/UjuRevToBidAdapter.xcframework/ios-arm64_x86_64-simulator/libUjuRevToBidAdapter-simulator.a \
-ObjC
// 其他必填项(与 UjuAdCore 一致)
SWIFT_ENABLE_EXPLICIT_MODULES = NO
ENABLE_DEBUG_DYLIB = NO为什么需要 -force_load 和 -ObjC?
-force_load:UjuRevToBidAdapter 与 UjuAdExt 均为 Swift 静态库,UjuAdExt 内部还包含 OC 静态库(Vendor 目录下的第三方 DSP SDK)。@objc(Custom*Adapter)标注的 OC 类符号在默认链接模式下会被 dead-strip,导致 ToBid SDK 通过NSClassFromString("CustomConfigAdapter")找不到适配器;UjuAdExt 内部 DSP SDK 的 OC 类符号也会被 dead-strip 导致竞价时_OBJC_CLASS_$_***未定义。-force_load强制保留全部 .o,是必需的。-ObjC:ToBid SDK(WindMillSDK)与 UjuAdExt 内部 DSP SDK 均为 OC 静态库,含 Category 扩展,必须加-ObjC才能正确加载 OC Category。
CocoaPods 集成模式下,podspec 已自动配置上述标志。
为什么需要 SWIFT_INCLUDE_PATHS?
UjuRevToBidAdapter 与 UjuAdExt 静态库(.a + Headers)不会自动将 Headers 目录加入 swiftmodule 搜索路径,必须显式指定,否则编译报 no such module 'UjuRevToBidAdapter' 或 no such module 'UjuAdExt'。
UjuAdExt Vendor 目录下的第三方 DSP SDK
UjuAdExt zip 内 Vendor/ 目录下的第三方 DSP SDK 为静态库(.framework 内含 .a),其 OC 类符号在默认链接模式下会被 dead-strip。若 zip 中已显式列出每个 DSP 的 .xcframework 路径,还需逐个 -force_load 每个 framework 内的二进制,缺一会导致对应 DSP 的 OC 类符号找不到。具体路径以解压后的目录结构为准。CocoaPods 集成模式下由 podspec 的 vendored_frameworks 自动处理。
步骤 4:验证集成
import UjuAdCore
print("UjuAdCore 版本: \(UjuAdCore.shared.getVersion())")
// 输出: UjuAdCore 版本: 3.4.2集成验证清单:
- [ ]
UjuAdCore.xcframework已添加到 Frameworks - [ ]
UjuAdExt.xcframework(含Vendor/下所有第三方 DSP xcframework)已添加到 Frameworks - [ ]
UjuRevToBidAdapter.xcframework已添加到 Frameworks - [ ] ToBid SDK 的 4 个 xcframework(WindMillSDK / WindFoundation / WindSDK / NexaX)已添加到 Frameworks
- [ ] 所有 xcframework 的 Embed 选项为 Do Not Embed
- [ ]
SWIFT_INCLUDE_PATHS已配置 UjuAdCore + UjuAdExt + UjuRevToBidAdapter 的 Headers - [ ]
OTHER_LDFLAGS已配置-force_load(三个 .a + Vendor 下 DSP 的 .a)+-ObjC - [ ]
SWIFT_ENABLE_EXPLICIT_MODULES设为NO - [ ]
ENABLE_DEBUG_DYLIB设为NO - [ ]
import UjuAdCore和import WindMillSDK编译通过
ToBid 后台配置
1. 添加自定义广告网络
在 ToBid 后台添加自定义广告网络,配置 ADN 级别的 CustomInfo(JSON 格式):
{
"appId": "YOUR_UJU_APP_ID",
"appKey": "YOUR_UJU_APP_KEY",
"channel": "tobid1"
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
appId | String | 是 | 优聚智汇后台分配的应用 ID |
appKey | String | 是 | 优聚智汇后台分配的 RSA 公钥,原样传入 |
channel | String | 否 | 渠道标识,用于数据统计与分账 |
appKey 是 RSA 公钥
appKey 实际是优聚智汇后台分配的 RSA 公钥(Base64 编码字符串),用于服务端通信加密与验签。请从优聚智汇开发者后台获取,原样传入,不要自行修改或截断。
2. 配置广告位
在 ToBid 后台为每个广告位配置 CustomInfo(JSON 格式):
{
"placementId": "YOUR_UJU_PLACEMENT_ID"
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
placementId | String | 是 | 优聚智汇后台分配的广告位 ID |
3. 配置适配器类名
ToBid 后台"自定义广告平台"配置时,需填写优聚适配器的 ObjC 类名(ToBid SDK 通过 NSClassFromString 反射发现):
| 适配器 | ObjC 类名 |
|---|---|
| 初始化适配器 | CustomConfigAdapter |
| 开屏广告适配器 | CustomSplashAdapter |
| 激励视频适配器 | CustomRewardAdapter |
| 插屏广告适配器 | CustomInterstitialAdapter |
| 原生广告适配器 | CustomNativeAdapter |
类名不可混淆
上述适配器类名通过 @objc(Custom*Adapter) 标注暴露给 ObjC runtime,集成方工程必须开启 -ObjC 链接标志(CocoaPods 自动配置,XCFramework 需手动添加),否则链接器会 dead-strip 这些类符号,运行时 ToBid SDK 无法发现适配器。
ToBid SDK 初始化
开发者只需初始化 ToBid SDK,优聚 SDK 的初始化由 ToBid 在启动时自动触发(通过适配器的初始化入口)。
1. 请求 ATT 授权(iOS 14+)
iOS 14+ 需在 Info.plist 添加 ATT 使用说明:
<key>NSUserTrackingUsageDescription</key>
<string>我们需要您的许可来提供更精准的广告体验</string>在 AppDelegate 中请求 ATT 授权,授权完成后再初始化 ToBid SDK(ATT 授权前网络请求可能被系统拒绝):
import UIKit
import AppTrackingTransparency
import WindMillSDK
import UjuAdCore // 用于解除日志 tag 过滤
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 解除 UjuAdCore 日志 tag 过滤(初始化后生效,便于排查)
UjuLogger.shared.setTagFilter(nil)
// 请求 ATT 授权,授权完成后再初始化 ToBid SDK
requestATTAndSetupToBid()
return true
}
private func requestATTAndSetupToBid() {
if #available(iOS 14, *) {
let status = ATTrackingManager.trackingAuthorizationStatus
if status == .notDetermined {
ATTrackingManager.requestTrackingAuthorization { [weak self] _ in
DispatchQueue.main.async {
self?.setupToBidSDK()
}
}
} else {
setupToBidSDK()
}
} else {
setupToBidSDK()
}
}
}2. 初始化 ToBid SDK
授权完成后调用 WindMillAds.setupSDK(appId:) 初始化 ToBid SDK:
private func setupToBidSDK() {
guard !ToBidConfig.tobidAppId.isEmpty else {
print("ToBid appId 未配置")
return
}
// 开启调试日志(调试阶段建议开启)
WindMillAds.setDebugEnable(true)
// 初始化 ToBid SDK,传入 ToBid 后台分配的 AppId
WindMillAds.setupSDK(appId: ToBidConfig.tobidAppId) { success, error in
DispatchQueue.main.async {
if success {
print("ToBid SDK 初始化成功,version=\(WindMillAds.sdkVersion())")
} else {
print("ToBid SDK 初始化失败: \(error?.localizedDescription ?? "unknown")")
}
}
}
}优聚 SDK 无需单独初始化
ToBid SDK 启动后,会自动调用 CustomConfigAdapter.initializeAdapter(configuration:) 方法,在适配器内部完成优聚 SDK 的 initialize + start 流程。开发者无需也不应调用 UjuAdCore.shared.initialize() / UjuAdCore.shared.start()。
初始化链路:
WindMillAds.setupSDK(appId:)启动 ToBid SDK- ToBid SDK 通过
NSClassFromString("CustomConfigAdapter")发现适配器 - 调用
CustomConfigAdapter.initializeAdapter(configuration:) - 适配器解析
custom_info获取优聚appId/appKey/channel - 调用
UjuAdCore.shared.initialize(...)+UjuAdCore.shared.start(listener) - 初始化完成后回调 ToBid bridge 通知初始化结果
广告加载示例
以下以激励视频为例,展示通过 ToBid 标准接口加载广告的完整流程。开屏、插屏、原生广告的调用方式类似,仅广告对象类名和监听协议不同。
import UIKit
import WindMillSDK
class RewardAdViewController: UIViewController {
// MARK: - 属性
/// ToBid 激励视频广告对象
private var rewardAd: WindMillRewardVideoAd?
/// 日志输出文本视图
private let logTextView = UITextView()
/// 展示按钮
private let showButton = UIButton(type: .system)
/// 广告是否已加载就绪
private var isReady = false
// MARK: - 加载广告
@objc private func loadAd() {
appendLog("开始加载激励视频广告: placementId=\(ToBidConfig.tobidRewardPlacementId)")
isReady = false
// 1. 创建广告请求
let request = WindMillAdRequest.request()
request.placementId = ToBidConfig.tobidRewardPlacementId // ToBid 后台广告位 ID
// 2. 创建 ToBid 激励视频广告对象
rewardAd = WindMillRewardVideoAd(request: request)
// 3. 设置代理
rewardAd?.delegate = self
// 4. 加载广告
rewardAd?.loadAdData()
}
// MARK: - 展示广告
@objc private func showAd() {
guard let ad = rewardAd, ad.isAdReady() else {
appendLog("广告未就绪,无法展示")
return
}
appendLog("展示激励视频广告")
ad.showAd(withRootViewController: self)
}
// MARK: - 资源释放
deinit {
// 销毁广告对象,释放资源
rewardAd = nil
}
private func appendLog(_ message: String) {
DispatchQueue.main.async { [weak self] in
self?.logTextView.text.append(message + "\n")
}
}
}
// MARK: - ToBid 激励视频监听回调
extension RewardAdViewController: WindMillRewardVideoAdDelegate {
func rewardVideoAdDidLoad(_ rewardVideoAd: WindMillRewardVideoAd) {
appendLog("✅ 广告加载成功")
isReady = true
}
func rewardVideoAdDidLoad(_ rewardVideoAd: WindMillRewardVideoAd, didFailWithError error: Error) {
appendLog("❌ 广告加载失败: \(error.localizedDescription)")
}
func rewardVideoAdDidVisible(_ rewardVideoAd: WindMillRewardVideoAd) {
appendLog("✅ 广告展示成功")
}
func rewardVideoAdDidShowFailed(_ rewardVideoAd: WindMillRewardVideoAd, error: Error) {
appendLog("❌ 广告展示失败: \(error.localizedDescription)")
}
func rewardVideoAdDidClick(_ rewardVideoAd: WindMillRewardVideoAd) {
appendLog("✅ 广告被点击")
}
func rewardVideoAdDidClose(_ rewardVideoAd: WindMillRewardVideoAd) {
appendLog("✅ 广告关闭")
isReady = false
}
func rewardVideoAd(_ rewardVideoAd: WindMillRewardVideoAd, reward: WindMillRewardInfo) {
appendLog("🎁 获得奖励: isCompletedView=\(reward.isCompeltedView)")
// 在此发放应用内奖励
}
func rewardVideoAd(_ rewardVideoAd: WindMillRewardVideoAd, didPlayFinishWithError error: Error?) {
appendLog("✅ 播放结束: \(error?.localizedDescription ?? "nil")")
}
}各广告类型对应类与协议
| 广告类型 | ToBid 广告类 | 监听协议 | 加载方法 |
|---|---|---|---|
| 开屏 | WindMillSplashAd | WindMillSplashAdDelegate | loadAd() |
| 激励视频 | WindMillRewardVideoAd | WindMillRewardVideoAdDelegate | loadAdData() |
| 插屏 | WindMillIntersititialAd | WindMillIntersititialAdDelegate | loadAdData() |
| 原生 | WindMillNativeAdsManager | WindMillNativeAdsManagerDelegate | loadAdData(count:) |
原生广告采用模板渲染
优聚原生广告采用模板渲染模式,由 SDK 内部渲染广告视图并绑定到 ToBid 提供的容器中。开发者无需自行绘制原生广告视图,NativeAdData 的字段(title/desc/iconUrl 等)可能为空。
Objective-C 宿主 App 接入
UjuRevToBidAdapter 为 Swift 静态库,但内部适配器类均标注 @objc(Custom*Adapter) 并继承 NSObject,ToBid SDK(WindMillSDK,OC 框架)通过 NSClassFromString 反射发现并调用。Objective-C 宿主 App 可直接接入,无需 Swift 桥接。
为什么 OC 宿主 App 无需 Swift 桥接?
- UjuRevToBidAdapter 的
Custom*Adapter类通过@objc标注暴露给 ObjC runtime,ToBid SDK 通过 runtime 发现并调用 - 宿主 App 不直接调用 UjuRevToBidAdapter 内部的 Swift 方法,只与 ToBid SDK 的 OC API 交互
- UjuAdCore 的初始化、日志配置均由
CustomConfigAdapter.initializeAdapter(configuration:)内部自动完成(含UjuLogger.shared.setTagFilter(nil)解除日志过滤) - UjuAdCore 未生成
-Swift.h桥接头(构建时SWIFT_INSTALL_OBJC_HEADER=NO),但这不影响 OC 宿主 App 接入,因为宿主 App 无需直接调用 UjuAdCore 的 Swift API
OC AppDelegate 示例
// AppDelegate.h
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@end// AppDelegate.m
#import "AppDelegate.h"
#import <AppTrackingTransparency/AppTrackingTransparency.h>
#import <WindMillSDK/WindMillSDK.h>
// ToBid 后台分配的 AppId(替换为自有 ToBid appId)
static NSString *const kToBidAppId = @"YOUR_TOBID_APP_ID";
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 请求 ATT 授权,授权完成后再初始化 ToBid SDK
// - iOS 14+: ATT 授权前网络请求可能被系统拒绝(Denied over Wi-Fi interface)
// - 授权完成后网络权限就绪,ToBid SDK config 请求才能成功
[self requestATTAndSetupToBid];
return YES;
}
#pragma mark - ATT + ToBid SDK Setup
- (void)requestATTAndSetupToBid {
if (@available(iOS 14, *)) {
ATTrackingManagerAuthorizationStatus status = [ATTrackingManager trackingAuthorizationStatus];
if (status == ATTrackingManagerAuthorizationStatusNotDetermined) {
// 未授权,请求授权
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setupToBidSDK];
});
}];
} else {
// 已授权或已拒绝,直接初始化
[self setupToBidSDK];
}
} else {
// iOS 13 及以下,无需 ATT 授权,直接初始化
[self setupToBidSDK];
}
}
- (void)setupToBidSDK {
if (kToBidAppId.length == 0) {
NSLog(@"ToBid appId 未配置,跳过 ToBid SDK 初始化");
return;
}
// 开启调试日志(调试阶段建议开启)
[WindMillAds setDebugEnable:YES];
// 初始化 ToBid SDK,传入 ToBid 后台分配的 AppId
// 优聚 SDK 的初始化由 CustomConfigAdapter 内部自动触发,无需手动调用
[WindMillAds setupSDKWithAppId:kToBidAppId callback:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (success) {
NSLog(@"ToBid SDK 初始化成功, version=%@", [WindMillAds sdkVersion]);
} else {
NSLog(@"ToBid SDK 初始化失败: %@", error.localizedDescription);
}
});
}];
}
@endOC 广告加载示例(激励视频)
// RewardAdViewController.m
#import "RewardAdViewController.h"
#import <WindMillSDK/WindMillSDK.h>
@interface RewardAdViewController () <WindMillRewardVideoAdDelegate>
@property (nonatomic, strong) WindMillRewardVideoAd *rewardAd;
@property (nonatomic, assign) BOOL isReady;
@end
@implementation RewardAdViewController
- (void)loadAd {
self.isReady = NO;
// 1. 创建广告请求
WindMillAdRequest *request = [WindMillAdRequest request];
request.placementId = @"YOUR_TOBID_REWARD_PLACEMENT_ID";
// 2. 创建 ToBid 激励视频广告对象
self.rewardAd = [[WindMillRewardVideoAd alloc] initWithRequest:request];
// 3. 设置代理
self.rewardAd.delegate = self;
// 4. 加载广告
[self.rewardAd loadAdData];
}
- (void)showAd {
if (!self.rewardAd || ![self.rewardAd isAdReady]) {
NSLog(@"广告未就绪,无法展示");
return;
}
[self.rewardAd showAdWithRootViewController:self];
}
#pragma mark - WindMillRewardVideoAdDelegate
- (void)rewardVideoAdDidLoad:(WindMillRewardVideoAd *)rewardVideoAd {
NSLog(@"✅ 广告加载成功");
self.isReady = YES;
}
- (void)rewardVideoAdDidLoad:(WindMillRewardVideoAd *)rewardVideoAd didFailWithError:(NSError *)error {
NSLog(@"❌ 广告加载失败: %@", error.localizedDescription);
}
- (void)rewardVideoAdDidVisible:(WindMillRewardVideoAd *)rewardVideoAd {
NSLog(@"✅ 广告展示成功");
}
- (void)rewardVideoAdDidClick:(WindMillRewardVideoAd *)rewardVideoAd {
NSLog(@"✅ 广告被点击");
}
- (void)rewardVideoAdDidClose:(WindMillRewardVideoAd *)rewardVideoAd {
NSLog(@"✅ 广告关闭");
self.isReady = NO;
}
- (void)rewardVideoAd:(WindMillRewardVideoAd *)rewardVideoAd reward:(WindMillRewardInfo *)reward {
NSLog(@"🎁 获得奖励: isCompletedView=%d", reward.isCompeltedView);
// 在此发放应用内奖励
}
@endOC 宿主 App 集成注意事项
| 事项 | 说明 |
|---|---|
| 集成方式 | 与 Swift 宿主 App 完全一致(CocoaPods / XCFramework) |
| Build Settings | 与 Swift 宿主 App 完全一致(-force_load + -ObjC + SWIFT_INCLUDE_PATHS) |
| Swift 运行时依赖 | Xcode 自动链接 Swift 运行时库,OC 宿主 App 无需手动配置 |
| UjuAdCore 调用 | 无需也不应直接调用 UjuAdCore 的 Swift API,初始化由适配器自动完成 |
| 日志调试 | 适配器初始化 UjuAdCore 时已自动调用 UjuLogger.shared.setTagFilter(nil) 解除日志过滤 |
| ToBid SDK API | 全部为 OC 接口,OC 宿主 App 直接调用即可 |
仍需配置 SWIFT_INCLUDE_PATHS
即使宿主 App 是纯 OC,编译 UjuRevToBidAdapter 静态库时仍需配置 SWIFT_INCLUDE_PATHS,因为 UjuRevToBidAdapter 的 swiftmodule 需要在编译期被解析。CocoaPods 集成模式下 podspec 已自动配置。
宿主 App 引入注意事项
引入 UjuRevToBidAdapter 时,宿主 App 的依赖关系如下:
| 依赖类型 | 内容 | 是否需要集成方引入 |
|---|---|---|
| 优聚核心 SDK | UjuAdCore ~> 3.4.2 | ✅ 必选,UjuRevToBidAdapter 强依赖 |
| 优聚预算扩展包 | UjuAdExt ~> 3.4.2(含 Vendor 下第三方 DSP SDK) | ✅ 必选,扩展广告填充来源 |
| ToBid 适配器 | UjuRevToBidAdapter 5.7.4 | ✅ 必选 |
| ToBid 聚合 SDK | ToBid-iOS 5.7.4(WindMillSDK + WindFoundation + WindSDK + NexaX) | ✅ 必选,集成方自行引入 |
| 第三方库 | gRPC-Swift / SwiftProtobuf / SwiftNIO | ❌ 已静态链接进 UjuAdCore,禁止重复引入 |
| 系统框架 | UIKit / Foundation / AVFoundation / CoreMedia / AdSupport / AppTrackingTransparency / StoreKit / CoreLocation | ✅ Xcode 自动链接 |
| 系统库 | libz.tbd / libresolv.tbd | ✅ Xcode 自动链接 |
Info.plist 必要配置
引入 UjuRevToBidAdapter 后,需在 Info.plist 中补充以下配置:
| 配置项 | 原因 |
|---|---|
NSUserTrackingUsageDescription | iOS 14+ 获取 IDFA 需用户授权(ToBid SDK + 优聚 SDK 均需) |
NSAppTransportSecurity.NSAllowsArbitraryLoads | ToBid SDK 与 UjuAdExt 内部 DSP SDK 均使用 HTTP 明文请求广告,iOS 默认强制 HTTPS 会报 -1022 |
NSMotionUsageDescription | UjuAdExt 内部 DSP SDK 读取 CoreMotion 设备信息 |
LSApplicationQueriesSchemes | UjuAdExt 内部 DSP SDK 广告跳转目标 App 的 URL Scheme 白名单(完整列表见 iOS 准备工作 - UjuAdExt 必要配置) |
SKAdNetworkItems | UjuAdExt 内部 DSP SDK 广告归因所需的 SKAdNetwork ID 列表(完整列表见 iOS 准备工作 - UjuAdExt 必要配置) |
依赖关系总览
宿主 App
│
├── ToBid-iOS(必选,集成方自行引入)
│ ├── WindMillSDK.xcframework
│ ├── WindFoundation.xcframework
│ ├── WindSDK.xcframework
│ └── NexaX.xcframework
│ ↓ 运行时通过 ObjC 反射发现适配器
├── UjuRevToBidAdapter 5.7.4(必选)
│ └── Custom*Adapter(@objc 暴露给 ToBid SDK)
│ ↓ 编译期依赖
├── UjuAdExt ~> 3.4.2(必选)
│ └── 内部 DSP 适配器(Vendor 下第三方 SDK 已打包,集成方无需单独获取)
│ ↓ 依赖
├── UjuAdCore ~> 3.4.2(必选)
│ ├── 优聚 Adx 竞价引擎
│ ├── 配置服务 / 埋点上报
│ └── 广告加载 / 渲染 / 展示
│ (gRPC-Swift / SwiftProtobuf / SwiftNIO 已静态链接,无需引入)
│
└── Info.plist 配置
├── NSUserTrackingUsageDescription
├── NSAppTransportSecurity.NSAllowsArbitraryLoads
├── NSMotionUsageDescription
├── LSApplicationQueriesSchemes
└── SKAdNetworkItems适配器架构说明
优聚 ToBid 适配器以独立 XCFramework(UjuRevToBidAdapter.xcframework)形式提供,实现 WindMillSDK 的 AWMCustom*Adapter 协议:
- 初始化适配器(
CustomConfigAdapter):实现 ToBid 的AWMCustomConfigAdapter协议,在 ToBid 启动时解析后台custom_info(appId/appKey/channel),完成优聚 SDK 的initialize+start流程,并通过 bridge 回调通知 ToBid 初始化结果 - 广告适配器(
CustomSplashAdapter/CustomRewardAdapter/CustomInterstitialAdapter/CustomNativeAdapter):每种广告类型对应一个独立的适配器类,实现 ToBid 的AWMCustom*Adapter协议,负责广告的加载、展示、销毁与事件回调转发 - 初始化等待机制:适配器内部维护初始化状态追踪(
CustomSdkInitHelper),各广告适配器在loadAd入口会等待优聚 SDK 初始化完成后再执行实际加载,避免 "no strategy" 错误 - 配置解析:统一解析 ToBid 后台下发的
custom_infoJSON 字符串,提取 appId/appKey/channel(ADN 级别)与 placementId(广告位级别) - Bridge 隔离:适配器通过
@objc标注暴露 OC 类名供 ToBid SDK 反射发现,bridge 回调由 ToBid SDK 在运行时传入
内部实现无需关注
适配器内部的所有类、方法与状态机均为 SDK 内部实现细节,开发者无需直接调用。开发者只需通过 ToBid 标准接口(WindMillAds / WindMillRewardVideoAd 等)使用广告即可。
常见问题
Q: 集成方需要单独初始化优聚 SDK 吗?
A: 不需要。ToBid SDK 启动后会自动触发优聚 SDK 的 initialize + start 流程。开发者只需初始化 ToBid SDK 即可。
Q: 广告加载时报 "no strategy" 或 "UjuAdCore init failed" 错误怎么办?
A: 该错误表示优聚 SDK 尚未初始化完成就调用了 loadAd。请检查:
- ToBid 后台 ADN 级别
custom_info中的appId/appKey是否正确 - 优聚后台是否已创建对应的应用和广告位
- 是否遗漏了 ATT 授权(iOS 14+ 必须先授权再初始化 ToBid SDK)
- 网络连接是否正常
Q: ToBid SDK 调用适配器报 "class not found" 怎么办?
A: 表示链接器 dead-strip 了适配器 OC 类符号。请检查:
OTHER_LDFLAGS是否已配置-force_load指向libUjuRevToBidAdapter-iphoneos.a/libUjuRevToBidAdapter-simulator.aOTHER_LDFLAGS是否已配置-ObjC(CocoaPods 自动配置,XCFramework 需手动添加)- 验证方法:在
AppDelegate中调用NSClassFromString("CustomConfigAdapter"),应返回非 nil
Q: UjuRevToBidAdapter 与 ToBid-iOS 的版本号必须一致吗?
A: 是的。UjuRevToBidAdapter 的版本号即对应适配的 ToBid SDK 版本号。集成时必须保证:
pod 'UjuRevToBidAdapter', '5.7.4'pod 'ToBid-iOS', '5.7.4'
两者版本号必须完全一致,否则可能因 ToBid SDK 协议变更导致适配器失效。
Q: 激励视频的奖励如何发放?
A: 优聚 SDK 的激励视频奖励回调由适配器内部处理并转发给 ToBid。开发者应在 ToBid 的激励视频代理回调(rewardVideoAd(_:reward:))中根据 reward.isCompeltedView 发放奖励,无需直接处理优聚 SDK 的监听器。
Q: 原生广告为什么是模板渲染模式?
A: 优聚原生广告采用模板渲染模式,由 SDK 内部渲染广告视图。适配器通过 ToBid 的 WindMillNativeAdData 协议将模板视图暴露给 ToBid 容器。NativeAdData 的字段(title/desc/iconUrl 等)可能为空,开发者无需自行渲染。
相关链接
- ToBid 自定义接入概述 — 反向集成模式、工作原理、后台配置、常见问题
- Android 接入指南 — Android 端接入步骤
- 自定义广告平台概述 — 反向集成概念说明
- iOS 准备工作 — 优聚 iOS SDK 标准集成(UjuAdCore 集成参考)
