跳转到主要内容
Trusted Arbiter 会比较已加载的 CloudX 出价和受支持的第三方出价,并返回被选中的平台。CloudX iOS SDK 3.4.0 支持 CloudX、Unity LevelPlay 和 PubMatic 出价输入。
请在 CloudX 初始化完成后再调用 CloudXCore.shared.arbiter(with:completion:)。在仲裁服务可用之前,SDK 会在传入的出价中回退选择可比较美元出价最高的平台。

基础 API

从已加载的广告创建出价候选项,然后传给仲裁器。
// cloudXAd 是 CloudX 加载回调中的 CLXAd 对象。
// levelPlayAdInfo 是 Unity LevelPlay 的广告信息对象。
// pubMaticPrice 和 pubMaticPartnerName 来自 PubMatic/OpenWrap 出价对象。
CLXArbiterBid *cloudXBid = [CLXArbiterBid cloudXBidWithAd:cloudXAd];

CLXArbiterBid *levelPlayBid =
    [CLXArbiterBid levelPlayBidWithNetworkName:levelPlayAdInfo.adNetwork
                                       revenue:levelPlayAdInfo.revenue.doubleValue
                                     precision:levelPlayAdInfo.precision];

CLXArbiterBid *pubMaticBid =
    [CLXArbiterBid pubMaticBidWithPrice:pubMaticPrice
                            partnerName:pubMaticPartnerName
                                 extras:nil];

CLXArbiterConfiguration *configuration =
    [CLXArbiterConfiguration configurationWithBids:@[cloudXBid, levelPlayBid, pubMaticBid]];

[[CloudXCore shared] arbiterWithConfiguration:configuration completion:^(CLXArbiterResult *result) {
    NSLog(@"Selected platform: %@", result.platform.name);
}];
CLXArbiterBid.cloudX 接收 CloudX 加载回调中的 CLXAd 对象。CLXArbiterBid.levelPlay 接收 Unity LevelPlay 广告信息值。CLXArbiterBid.pubMatic 接收 PubMatic OpenWrap 出价价格和可选的合作伙伴名称。extras 映射在 LevelPlay 和 PubMatic 出价中均为可选参数,partnerName 在 PubMatic 出价中为可选参数。完成回调在主线程上执行,因此你可以直接在其中展示广告或更新 UI。 result.platform 在选中平台时为 CLXArbiterPlatform.cloudXlevelPlaypubMatic;当无法选出获胜平台时(例如未传入任何出价),则为 CLXArbiterPlatform.none

分步示例:在 CloudX 与 LevelPlay 之间仲裁

本演练展示了应读取哪个 Unity LevelPlay 回调,以及应将哪些值传入仲裁器。示例使用插屏广告,但相同的字段映射适用于任何广告格式。
1

加载两个候选项

创建 CloudX 和 LevelPlay 插屏广告,设置各自的 delegate,并为每个平台启动加载。
self.cloudXInterstitial = [[CloudXCore shared] createInterstitialWithAdUnitId:@"YOUR_CLOUDX_AD_UNIT_ID"];
self.cloudXInterstitial.delegate = self;
[self.cloudXInterstitial load];

self.levelPlayInterstitial = [[LPMInterstitialAd alloc] initWithAdUnitId:@"YOUR_LEVELPLAY_AD_UNIT_ID"];
self.levelPlayInterstitial.delegate = self;
[self.levelPlayInterstitial loadAd];
2

保存每个平台已加载的广告

LevelPlay 在其加载回调中提供 LPMAdInfo;CloudX 提供 CLXAd。请保存两者,下一步会从中读取仲裁输入。
// 属性:@property (nonatomic, strong) CLXAd *cloudXAd;
//       @property (nonatomic, strong) LPMAdInfo *levelPlayInfo;

// CLXInterstitialDelegate
- (void)didLoadAd:(CLXAd *)ad {
    self.cloudXAd = ad;
}

// LPMInterstitialAdDelegate
- (void)didLoadAdWithAdInfo:(LPMAdInfo *)adInfo {
    self.levelPlayInfo = adInfo;
}
3

将值映射为出价

LPMAdInfo 读取 LevelPlay 字段,并传给 CLXArbiterBid.levelPlay。CloudX 出价直接接收 CLXAd。只提交实际加载成功的平台。
LPMAdInfo 字段类型CLXArbiterBid.levelPlay 参数
adNetworkNSString *networkName
revenueNSNumber *revenue(用 .doubleValue 解包)
precisionNSString *precision
NSMutableArray<CLXArbiterBid *> *bids = [NSMutableArray array];

if (self.cloudXAd) {
    [bids addObject:[CLXArbiterBid cloudXBidWithAd:self.cloudXAd]];
}

if (self.levelPlayInfo) {
    CLXArbiterBid *levelPlayBid =
        [CLXArbiterBid levelPlayBidWithNetworkName:self.levelPlayInfo.adNetwork
                                           revenue:self.levelPlayInfo.revenue.doubleValue
                                         precision:self.levelPlayInfo.precision];
    [bids addObject:levelPlayBid];
}

CLXArbiterConfiguration *configuration =
    [CLXArbiterConfiguration configurationWithBids:bids];
4

运行仲裁器

将 configuration 连同完成回调传给仲裁器。请在两个插屏广告都加载完成(或加载失败)后再运行:跟踪各自的加载回调和加载失败,并且只提交成功加载的候选项。完成回调在主线程上执行。
[[CloudXCore shared] arbiterWithConfiguration:configuration completion:^(CLXArbiterResult *result) {
    [self showWinner:result];
}];
5

展示获胜平台

result.platform.name 与平台常量比较,展示获胜平台的广告。CLXArbiterPlatform.none 表示未选出获胜平台,此时不展示广告,继续应用流程。
- (void)showWinner:(CLXArbiterResult *)result {
    NSString *platform = result.platform.name;
    if ([platform isEqualToString:CLXArbiterPlatform.cloudX.name]) {
        [self.cloudXInterstitial showFromViewController:self];
    } else if ([platform isEqualToString:CLXArbiterPlatform.levelPlay.name]) {
        [self.levelPlayInterstitial showAdWithViewController:self placementName:nil];
    }
    // CLXArbiterPlatform.none: 无获胜平台;不展示广告
}
LPMAdInfo.revenueNSNumber 且可能缺失,因此在传给非空的 revenue: 参数之前请将其解包为 double:Objective-C 中使用 revenue.doubleValue,Swift 中使用 revenue?.doubleValue ?? 0
下方的 ArbiterInterstitialController 将上述步骤封装为一个可复用的组件,会在到达广告位之前提前准备好获胜平台。

插屏广告示例

这个插屏广告示例会在两个平台之间仲裁:CloudX 和 Unity LevelPlay。在到达广告位之前先准备好获胜平台:
  1. 并行加载 CloudX 和 LevelPlay。
  2. 等待两个平台都加载完成或加载失败。
  3. 只将已加载的候选项提交给 Trusted Arbiter。
  4. 缓存选中的平台。
  5. 到达广告位时,立即展示缓存的获胜广告。
如果两个平台都加载失败,开始新的加载周期。如果到达广告位时还没有准备好获胜平台,则继续应用流程,不展示广告。
ArbiterInterstitialController.swift
/// 提前准备好 Trusted Arbiter 的获胜平台,以便在到达广告位时立即展示插屏广告。
///
/// 并行加载 CloudX 和 LevelPlay 插屏广告,等待两者都加载完成或加载失败,
/// 将已加载的候选项提交给 CloudXCore.shared.arbiter,并将选中的
/// CLXArbiterPlatform 缓存到 nextWinner 中。
final class ArbiterInterstitialController: NSObject {
    protocol Listener: AnyObject {
        /// 当仲裁器为下一次展示选出平台后调用。
        func arbiterInterstitialController(
            _ controller: ArbiterInterstitialController,
            didPrepareWinner platform: CLXArbiterPlatform
        )
    }

    weak var listener: Listener?

    private let cloudXInterstitial: CLXInterstitial
    private let levelPlayInterstitial: LPMInterstitialAd
    private var cloudXAd: CLXAd?
    private var cloudXLoadDone = false
    private var levelPlayAdInfo: LPMAdInfo?
    private var levelPlayLoadDone = false
    private var nextWinner: CLXArbiterPlatform?

    init(cloudXInterstitial: CLXInterstitial, levelPlayInterstitial: LPMInterstitialAd) {
        self.cloudXInterstitial = cloudXInterstitial
        self.levelPlayInterstitial = levelPlayInterstitial
        super.init()
        self.cloudXInterstitial.delegate = self
        self.levelPlayInterstitial.setDelegate(self)
    }

    /// 为每个当前没有缓存广告的平台启动加载。
    func loadMissingAds() {
        if cloudXAd == nil { cloudXInterstitial.load() }
        if levelPlayAdInfo == nil { levelPlayInterstitial.loadAd() }
    }

    /// 展示已准备好的获胜广告,仅当确实发起了展示调用时返回 true。
    ///
    /// 当没有准备好获胜平台或缓存的广告已不可用时返回 false,
    /// 此时会启动一次新的加载周期。
    func showAtPlacement(from viewController: UIViewController, placementName: String? = nil) -> Bool {
        guard let platformName = nextWinner?.name else { return false }

        if platformName == CLXArbiterPlatform.cloudX.name {
            return showCloudX(from: viewController, placementName: placementName)
        }

        if platformName == CLXArbiterPlatform.levelPlay.name {
            return showLevelPlay(from: viewController, placementName: placementName)
        }

        return false
    }

    /// 在两个平台都完成后运行仲裁器,然后缓存获胜平台。
    ///
    /// 在两个加载都完成之前提前返回。如果两个平台都没有加载成功,则重启加载周期;
    /// 否则将已加载的候选项提交给 CloudXCore.shared.arbiter。
    private func maybePrepareWinner() {
        guard cloudXLoadDone, levelPlayLoadDone else { return }

        if cloudXAd == nil && levelPlayAdInfo == nil {
            cloudXLoadDone = false
            levelPlayLoadDone = false
            loadMissingAds()
            return
        }

        var bids: [CLXArbiterBid] = []
        if let cloudXAd {
            bids.append(CLXArbiterBid.cloudX(ad: cloudXAd))
        }

        if let levelPlayAdInfo {
            bids.append(CLXArbiterBid.levelPlay(
                networkName: levelPlayAdInfo.adNetwork,
                revenue: levelPlayAdInfo.revenue?.doubleValue ?? 0,
                precision: levelPlayAdInfo.precision
            ))
        }

        let configuration = CLXArbiterConfiguration.configuration(bids: bids, builderBlock: nil)
        CloudXCore.shared.arbiter(with: configuration) { [weak self] result in
            guard let self else { return }
            nextWinner = result.platform
            listener?.arbiterInterstitialController(self, didPrepareWinner: result.platform)
        }
    }

    private func showCloudX(from viewController: UIViewController, placementName: String?) -> Bool {
        if cloudXInterstitial.isReady {
            if let placementName {
                cloudXInterstitial.show(from: viewController, placement: placementName, customData: nil)
            } else {
                cloudXInterstitial.show(from: viewController)
            }
            return true
        }

        clearCloudXAndLoadMissingAds()
        return false
    }

    private func showLevelPlay(from viewController: UIViewController, placementName: String?) -> Bool {
        if levelPlayInterstitial.isAdReady() {
            levelPlayInterstitial.showAd(viewController: viewController, placementName: placementName)
            return true
        }

        clearLevelPlayAndLoadMissingAds()
        return false
    }

    private func clearCloudXAndLoadMissingAds() {
        cloudXAd = nil
        cloudXLoadDone = false
        nextWinner = nil
        loadMissingAds()
    }

    private func clearLevelPlayAndLoadMissingAds() {
        levelPlayAdInfo = nil
        levelPlayLoadDone = false
        nextWinner = nil
        loadMissingAds()
    }
}

extension ArbiterInterstitialController: CLXInterstitialDelegate {
    func didLoad(_ ad: CLXAd) {
        cloudXAd = ad
        cloudXLoadDone = true
        maybePrepareWinner()
    }

    func didFailToLoadAd(_ adUnitId: String, error: CLXError) {
        cloudXAd = nil
        cloudXLoadDone = true
        maybePrepareWinner()
    }

    func didDisplay(_ ad: CLXAd) {}

    func didFailToDisplay(_ ad: CLXAd, error: CLXError) {
        clearCloudXAndLoadMissingAds()
    }

    func didHide(_ ad: CLXAd) {
        clearCloudXAndLoadMissingAds()
    }

    func didClick(_ ad: CLXAd) {}
}

extension ArbiterInterstitialController: LPMInterstitialAdDelegate {
    func didLoadAd(with adInfo: LPMAdInfo) {
        levelPlayAdInfo = adInfo
        levelPlayLoadDone = true
        maybePrepareWinner()
    }

    func didFailToLoadAd(withAdUnitId adUnitId: String, error: Error) {
        levelPlayAdInfo = nil
        levelPlayLoadDone = true
        maybePrepareWinner()
    }

    func didChangeAdInfo(_ adInfo: LPMAdInfo) {
        levelPlayAdInfo = adInfo
    }

    func didDisplayAd(with adInfo: LPMAdInfo) {}

    func didFailToDisplayAd(with adInfo: LPMAdInfo, error: Error) {
        clearLevelPlayAndLoadMissingAds()
    }

    func didCloseAd(with adInfo: LPMAdInfo) {
        clearLevelPlayAndLoadMissingAds()
    }

    func didClickAd(with adInfo: LPMAdInfo) {}
}
showAtPlacement(from:placementName:) 仅当确实发起了展示调用时返回 truedidChangeAdInfo(_:) 会在 LevelPlay 广告保持加载状态期间更新缓存的 LevelPlay 候选项值。 对于 PubMatic OpenWrap,使用 CLXArbiterBid.pubMatic(price:partnerName:extras:) 创建第三方出价。如果仲裁服务不可用,SDK 会在传入的受支持出价输入中回退选择可比较美元出价最高的平台。