Skip to content

ToBid iOS 接入指南

本文档指导开发者在 iOS 端将优聚智汇 SDK 以**自定义广告网络(Custom Adapter)**形式接入 ToBid(Sigmob WindMill)聚合平台。

阅读前置

请先阅读 ToBid 自定义接入概述,了解反向集成模式、工作原理、后台配置与常见问题。本文仅介绍 iOS 端的依赖引入、dead-strip 防护、SDK 初始化与广告加载。

依赖引入

1. 引入优聚 SDK 核心库

确保工程已通过 SPM 引入优聚智汇 SDK 核心库(UjuAdSdk),详见 iOS 集成说明

2. 引入优聚 ToBid 适配器

优聚 ToBid 适配器以独立 SPM Package 形式提供,包名为 ToBidAdapter,依赖 UjuAdSdk 核心库。

在 Xcode 中通过 File → Add Packages → Add Local... 添加 ToBidAdapter 目录,或在 Package.swift 中添加本地依赖:

swift
// Package.swift
dependencies: [
    // 依赖优聚 SDK 核心库(位于同级目录)
    .package(path: "../"),
    // 依赖 ToBid 适配器
    .package(path: "../Adapters/ToBidAdapter/")
],
targets: [
    .target(
        name: "YourApp",
        dependencies: [
            .product(name: "UjuAdSdk", package: "UjuAdSdk"),
            .product(name: "ToBidAdapter", package: "ToBidAdapter")
        ]
    )
]

3. 引入 ToBid 聚合 SDK

ToBid(Sigmob WindMill)SDK 需通过 CocoaPods 或 SPM 单独引入,具体引入方式参考 Sigmob 官方文档。适配器在编译期不依赖 WindMillSDK,仅在运行时由 WindMillSDK 通过 ObjC 运行时动态发现并调用适配器类。

防止 dead-strip(必须)

WindMillSDK 通过 ObjC 运行时(NSClassFromString)动态发现优聚 ToBid 适配器类。由于适配器在编译期不被任何代码直接引用,链接器可能会将其视为无用代码而移除(dead-strip),导致运行时找不到适配器。

必须在 App 启动时调用一次 ToBidAdapter.ensureLinked(),确保适配器模块被链接进宿主二进制:

swift
import UIKit
import ToBidAdapter

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {

        // 确保 ToBidAdapter 模块被链接进二进制
        // WindMillSDK 通过运行时发现 Custom*Adapter 类,需保证模块代码未被 dead-strip
        _ = ToBidAdapter.ensureLinked()

        // 初始化 ToBid SDK
        initToBidSdk()

        return true
    }
}

不可省略

若忘记调用 ensureLinked(),WindMillSDK 在运行时将无法发现优聚适配器,表现为优聚 ADN 永远无法初始化、广告位无法加载。

ToBid SDK 初始化

开发者只需初始化 ToBid SDK,优聚 SDK 的初始化由 ToBid 在启动时自动触发(通过适配器的初始化入口)。

AppDelegate.application(_:didFinishLaunchingWithOptions:) 中初始化 ToBid:

swift
import UIKit
import ToBidAdapter

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {

        // 1. 确保 ToBidAdapter 模块被链接进二进制
        _ = ToBidAdapter.ensureLinked()

        // 2. 初始化 ToBid SDK
        initToBidSdk()

        return true
    }

    // MARK: - 初始化 ToBid SDK

    /// 初始化 ToBid 聚合 SDK
    ///
    /// 步骤:
    /// 1. 获取 WindMillAd 单例
    /// 2. 配置个性化推荐、Debug、成年等参数
    /// 3. 调用 startWithAppId 启动 SDK,传入 AppId 和初始化回调
    private func initToBidSdk() {
        print("[ToBidDemo] initToBidSdk 开始")

        // 接入真实 WindMillSDK 后的完整实现:
        // let ads = WindMillAd.sharedAds()
        // // 是否成年(可选参数,默认是成年用户)
        // ads.setAdult(true)
        // // 是否开启个性化推荐(可选参数,默认开启)
        // ads.setPersonalizedAdvertisingOn(true)
        // // 开启 Debug 日志(调试阶段建议开启)
        // ads.setDebugEnable(true)
        //
        // // 启动 ToBid SDK,传入 ToBid 后台分配的 AppId
        // ads.startWithAppId(self, ToBidDemoConfig.TOBID_APP_ID, listener: { success, errorMsg in
        //     if success {
        //         print("ToBid SDK 初始化成功")
        //     } else {
        //         print("ToBid SDK 初始化失败: \(errorMsg ?? "未知错误")")
        //     }
        // })
    }
}

优聚 SDK 无需单独初始化

ToBid SDK 启动后会自动触发优聚 SDK 的 init + start 流程,开发者无需不应调用 UjuAdSdk.shared.init() / UjuAdSdk.shared.start()

广告加载示例

以下以激励视频为例,展示通过 ToBid 标准接口加载广告的完整流程。开屏、插屏、横幅、原生广告的调用方式类似,仅广告对象类名不同。

swift
import UIKit
// import WindMillSDK  // 接入真实 WindMillSDK 后引入

class RewardAdViewController: UIViewController {

    // MARK: - 属性

    /// 日志输出文本视图
    private let logTextView = UITextView()
    /// 展示按钮
    private let showButton = UIButton(type: .system)
    /// 广告是否已加载就绪
    private var isReady = false

    /// ToBid 激励视频广告对象(WindMillSDK 类型)
    private var rewardAd: AnyObject?

    // MARK: - 生命周期

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "激励视频广告测试"
        view.backgroundColor = .white
        setupUI()
    }

    // MARK: - 加载广告

    @objc private func loadAd() {
        appendLog("开始加载激励视频广告: placementId=\(ToBidDemoConfig.REWARD_PLACEMENT_ID)")
        isReady = false
        updateShowButton()

        // 接入真实 WindMillSDK 后的实际调用:
        // let request = WMRewardAdRequest(ToBidDemoConfig.REWARD_PLACEMENT_ID, "", nil)
        // rewardAd = WMRewardAd(self, request)
        // rewardAd?.setRewardedAdListener(self)
        // rewardAd?.loadAd()
    }

    // MARK: - 展示广告

    @objc private func showAd() {
        // 空指针检查:广告未就绪不展示
        guard isReady else {
            appendLog("广告未就绪,无法展示")
            return
        }
        appendLog("展示激励视频广告")
        // 接入真实 WindMillSDK 后:rewardAd?.show(self, nil)
    }

    // MARK: - 资源释放

    deinit {
        // 销毁广告对象,释放资源
        // rewardAd?.destroy()
    }

    // MARK: - UI 布局(略)

    private func setupUI() {
        // UI 布局代码略,开发者按需实现
    }

    private func updateShowButton() {
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            self.showButton.isEnabled = self.isReady
        }
    }

    private func appendLog(_ message: String) {
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            self.logTextView.text.append(message + "\n")
        }
    }
}

// MARK: - ToBid 激励视频监听回调(接入真实 WindMillSDK 后实现)

/// 接入真实 WindMillSDK 后,让 ViewController 符合 WMRewardAdListener 协议:
///
/// extension RewardAdViewController: WMRewardAdListener {
///     func onVideoAdLoadSuccess(_ placementId: String?) {
///         appendLog("广告加载成功: \(placementId ?? "")")
///         isReady = true
///         updateShowButton()
///     }
///
///     func onVideoAdLoadError(_ error: WindMillError?, placementId: String?) {
///         appendLog("广告加载失败: \(error?.errorCode ?? 0)-\(error?.message ?? "")")
///     }
///
///     func onVideoAdPlayStart(_ adInfo: AdInfo?) {
///         appendLog("广告开始播放")
///     }
///
///     func onVideoAdPlayEnd(_ adInfo: AdInfo?) {
///         appendLog("广告播放结束")
///     }
///
///     func onVideoAdClicked(_ adInfo: AdInfo?) {
///         appendLog("广告被点击")
///     }
///
///     func onVideoRewarded(_ adInfo: AdInfo?, rewardInfo: WMRewardInfo?) {
///         appendLog("获得奖励: isReward=\(rewardInfo?.isReward() ?? false)")
///         // 在此发放应用内奖励
///     }
///
///     func onVideoAdClosed(_ adInfo: AdInfo?) {
///         appendLog("广告关闭")
///         isReady = false
///         updateShowButton()
///     }
/// }

各广告类型对应协议

广告类型ToBid 广告类监听协议
开屏WMSplashAdWMSplashAdListener
激励视频WMRewardAdWMRewardAdListener
插屏WMInterstitialAdWMInterstitialAdListener
横幅WMBannerAdWMBannerAdListener
原生WMNativeAdWMNativeAdListener

模板渲染模式

优聚原生广告采用模板渲染模式,由 SDK 内部渲染广告视图并绑定到 ToBid 提供的容器中。开发者无需自行绘制原生广告视图,NativeAdData 的字段(title/desc/iconUrl 等)可能为空。

适配器架构说明

优聚 ToBid 适配器以独立 SPM Package(ToBidAdapter)形式提供,实现 WindMillSDK 的 AWMCustom*Adapter 协议:

  • 适配器入口ToBidAdapter 模块提供 ensureLinked() 方法防止 dead-strip,并提供版本号查询
  • 初始化适配器:实现 ToBid 的配置适配器协议,在 ToBid 启动时解析后台 CustomInfo(appId/appKey/channel),完成优聚 SDK 的 init + start 流程,并通过 bridge 回调通知 ToBid 初始化结果
  • 广告适配器:每种广告类型(开屏/激励/插屏/横幅/原生)对应一个独立的适配器类,实现 ToBid 的 AWMCustom*Adapter 协议,负责广告的加载、展示、销毁与事件回调转发
  • 初始化等待机制:适配器内部维护初始化状态追踪,各广告适配器在 loadAd 入口会等待优聚 SDK 初始化完成后再执行实际加载,避免 "no strategy" 错误
  • 配置解析:统一解析 ToBid 后台下发的 CustomInfo JSON 字符串,提取 appId/appKey/channel(ADN 级别)与 placementId(广告位级别)
  • Bridge 隔离:适配器在编译期不依赖 WindMillSDK,通过运行时机制调用 bridge 回调;接入真实 WindMillSDK 后可将 bridge 类型从运行时调用改为具体协议方法调用

内部实现无需关注

适配器内部的所有类、方法与状态机均为 SDK 内部实现细节,开发者无需直接调用。开发者只需通过 ToBid 标准接口(WindMillAd / WMRewardAd 等)使用广告,并在 App 启动时调用 ToBidAdapter.ensureLinked() 即可。

相关链接