> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cloudx.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Trusted Arbiter

> 在 iOS 应用中比较 CloudX 出价与受支持的第三方出价

Trusted Arbiter 会比较已加载的 CloudX 出价和受支持的第三方出价，并返回被选中的平台。CloudX iOS SDK 3.4.0 支持 CloudX、Unity LevelPlay 和 PubMatic 出价输入。

<Info>
  请在 CloudX 初始化完成后再调用 `CloudXCore.shared.arbiter(with:completion:)`。在仲裁服务可用之前，SDK 会在传入的出价中回退选择可比较美元出价最高的平台。
</Info>

## 基础 API

从已加载的广告创建出价候选项，然后传给仲裁器。

<CodeGroup>
  ```objc Objective-C theme={null}
  // 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);
  }];
  ```

  ```swift Swift theme={null}
  // cloudXAd 是 CloudX 加载回调中的 CLXAd 对象。
  // levelPlayAdInfo 是 Unity LevelPlay 的广告信息对象。
  // pubMaticPrice 和 pubMaticPartnerName 来自 PubMatic/OpenWrap 出价对象。
  let cloudXBid = CLXArbiterBid.cloudX(ad: cloudXAd)

  let levelPlayBid = CLXArbiterBid.levelPlay(
      networkName: levelPlayAdInfo.adNetwork,
      revenue: levelPlayAdInfo.revenue?.doubleValue ?? 0,
      precision: levelPlayAdInfo.precision
  )

  let pubMaticBid = CLXArbiterBid.pubMatic(
      price: pubMaticPrice,
      partnerName: pubMaticPartnerName,
      extras: nil
  )

  let configuration = CLXArbiterConfiguration.configuration(
      bids: [cloudXBid, levelPlayBid, pubMaticBid],
      builderBlock: nil
  )

  CloudXCore.shared.arbiter(with: configuration) { result in
      print("Selected platform: \(result.platform.name)")
  }
  ```
</CodeGroup>

`CLXArbiterBid.cloudX` 接收 CloudX 加载回调中的 `CLXAd` 对象。`CLXArbiterBid.levelPlay` 接收 Unity LevelPlay 广告信息值。`CLXArbiterBid.pubMatic` 接收 PubMatic OpenWrap 出价价格和可选的合作伙伴名称。`extras` 映射在 LevelPlay 和 PubMatic 出价中均为可选参数，`partnerName` 在 PubMatic 出价中为可选参数。完成回调在主线程上执行，因此你可以直接在其中展示广告或更新 UI。

`result.platform` 在选中平台时为 `CLXArbiterPlatform.cloudX`、`levelPlay` 或 `pubMatic`；当无法选出获胜平台时（例如未传入任何出价），则为 `CLXArbiterPlatform.none`。

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

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

<Steps>
  <Step title="加载两个候选项">
    创建 CloudX 和 LevelPlay 插屏广告，设置各自的 delegate，并为每个平台启动加载。

    <CodeGroup>
      ```objc Objective-C theme={null}
      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];
      ```

      ```swift Swift theme={null}
      cloudXInterstitial = CloudXCore.shared.createInterstitial(adUnitId: "YOUR_CLOUDX_AD_UNIT_ID")
      cloudXInterstitial?.delegate = self
      cloudXInterstitial?.load()

      levelPlayInterstitial = LPMInterstitialAd(adUnitId: "YOUR_LEVELPLAY_AD_UNIT_ID")
      levelPlayInterstitial.setDelegate(self)
      levelPlayInterstitial.loadAd()
      ```
    </CodeGroup>
  </Step>

  <Step title="保存每个平台已加载的广告">
    LevelPlay 在其加载回调中提供 `LPMAdInfo`；CloudX 提供 `CLXAd`。请保存两者，下一步会从中读取仲裁输入。

    <CodeGroup>
      ```objc Objective-C theme={null}
      // 属性：@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;
      }
      ```

      ```swift Swift theme={null}
      var cloudXAd: CLXAd?
      var levelPlayInfo: LPMAdInfo?

      // CLXInterstitialDelegate
      func didLoad(_ ad: CLXAd) {
          cloudXAd = ad
      }

      // LPMInterstitialAdDelegate
      func didLoadAd(with adInfo: LPMAdInfo) {
          levelPlayInfo = adInfo
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="将值映射为出价">
    从 `LPMAdInfo` 读取 LevelPlay 字段，并传给 `CLXArbiterBid.levelPlay`。CloudX 出价直接接收 `CLXAd`。只提交实际加载成功的平台。

    | `LPMAdInfo` 字段 | 类型           | `CLXArbiterBid.levelPlay` 参数   |
    | -------------- | ------------ | ------------------------------ |
    | `adNetwork`    | `NSString *` | `networkName`                  |
    | `revenue`      | `NSNumber *` | `revenue`（用 `.doubleValue` 解包） |
    | `precision`    | `NSString *` | `precision`                    |

    <CodeGroup>
      ```objc Objective-C theme={null}
      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];
      ```

      ```swift Swift theme={null}
      var bids: [CLXArbiterBid] = []

      if let cloudXAd {
          bids.append(CLXArbiterBid.cloudX(ad: cloudXAd))
      }

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

      let configuration = CLXArbiterConfiguration.configuration(
          bids: bids,
          builderBlock: nil
      )
      ```
    </CodeGroup>
  </Step>

  <Step title="运行仲裁器">
    将 configuration 连同完成回调传给仲裁器。请在两个插屏广告都加载完成（或加载失败）后再运行：跟踪各自的加载回调和加载失败，并且只提交成功加载的候选项。完成回调在主线程上执行。

    <CodeGroup>
      ```objc Objective-C theme={null}
      [[CloudXCore shared] arbiterWithConfiguration:configuration completion:^(CLXArbiterResult *result) {
          [self showWinner:result];
      }];
      ```

      ```swift Swift theme={null}
      CloudXCore.shared.arbiter(with: configuration) { [weak self] result in
          self?.showWinner(result)
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="展示获胜平台">
    将 `result.platform.name` 与平台常量比较，展示获胜平台的广告。`CLXArbiterPlatform.none` 表示未选出获胜平台，此时不展示广告，继续应用流程。

    <CodeGroup>
      ```objc Objective-C theme={null}
      - (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: 无获胜平台；不展示广告
      }
      ```

      ```swift Swift theme={null}
      func showWinner(_ result: CLXArbiterResult) {
          switch result.platform.name {
          case CLXArbiterPlatform.cloudX.name:
              cloudXInterstitial?.show(from: self)
          case CLXArbiterPlatform.levelPlay.name:
              levelPlayInterstitial.showAd(viewController: self, placementName: nil)
          default:
              break // CLXArbiterPlatform.none: 无获胜平台；不展示广告
          }
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

<Note>
  `LPMAdInfo.revenue` 是 `NSNumber` 且可能缺失，因此在传给非空的 `revenue:` 参数之前请将其解包为 `double`：Objective-C 中使用 `revenue.doubleValue`，Swift 中使用 `revenue?.doubleValue ?? 0`。
</Note>

下方的 `ArbiterInterstitialController` 将上述步骤封装为一个可复用的组件，会在到达广告位之前提前准备好获胜平台。

## 插屏广告示例

这个插屏广告示例会在两个平台之间仲裁：CloudX 和 Unity LevelPlay。在到达广告位之前先准备好获胜平台：

1. 并行加载 CloudX 和 LevelPlay。
2. 等待两个平台都加载完成或加载失败。
3. 只将已加载的候选项提交给 Trusted Arbiter。
4. 缓存选中的平台。
5. 到达广告位时，立即展示缓存的获胜广告。

如果两个平台都加载失败，开始新的加载周期。如果到达广告位时还没有准备好获胜平台，则继续应用流程，不展示广告。

```swift ArbiterInterstitialController.swift theme={null}
/// 提前准备好 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:)` 仅当确实发起了展示调用时返回 `true`。`didChangeAdInfo(_:)` 会在 LevelPlay 广告保持加载状态期间更新缓存的 LevelPlay 候选项值。

对于 PubMatic OpenWrap，使用 `CLXArbiterBid.pubMatic(price:partnerName:extras:)` 创建第三方出价。如果仲裁服务不可用，SDK 会在传入的受支持出价输入中回退选择可比较美元出价最高的平台。
