概览

CloudX iOS SDK 设置和核心功能概览

需要 iOS 13.0+、Xcode 16.0+ 和 Swift 6.0+。

安装

Swift Package Manager

请参阅 Swift Package Manager 安装指南,了解受支持的软件包、精确的适配器版本和项目要求。

CocoaPods

Podfile
platform :ios, '15.0'

target 'YourApp' do
  use_frameworks!

  # 核心 SDK
  pod 'CloudXCore', '~> 3.11.0'

  # 广告网络适配器(根据需要添加)。
  # 每个适配器独立按 <网络 SDK 版本>.<适配器修订号> 版本化,
  # 并针对该确切网络 SDK 版本构建。
  pod 'CloudXBigoAdapter', '~> 6.1.0.0'           # BigoADS 6.1.0
  pod 'CloudXMetaAdapter', '~> 6.22.0.1'            # FBAudienceNetwork 6.22.0
  pod 'CloudXVungleAdapter', '~> 7.7.6.1'           # VungleAds 7.7.6
  pod 'CloudXInMobiAdapter', '~> 11.5.0.0'          # InMobiSDK 11.5.0
  pod 'CloudXMintegralAdapter', '~> 8.1.7.0'        # MintegralAdSDK 8.1.7
  pod 'CloudXUnityAdsAdapter', '~> 4.20.1.1'        # UnityAds 4.20.1
  pod 'CloudXMagniteAdapterV2', '~> 1.0.4.2'        # MagniteSDK 1.0.4
  pod 'CloudXMobileFuseAdapter', '~> 1.12.0.0'      # MobileFuseSDK 1.12.0
  pod 'CloudXMolocoAdapter', '~> 4.11.1.1'           # MolocoSDKiOS 4.11.1
  pod 'CloudXVerveAdapter', '~> 3.9.0.1'            # HyBid 3.9.0
  pod 'CloudXDigitalTurbineAdapter', '~> 8.4.10.1'  # Fyber Marketplace SDK 8.4.10
  pod 'CloudXGoogleWaterfallAdapter', '~> 13.9.0.0' # Google Mobile Ads SDK 13.9.0
  pod 'CloudXPangleAdapter', '~> 8.3.0.6.1'         # Ads-Global(Pangle / 字节跳动)8.3.0.6
  pod 'CloudXTaurusXAdapter', '~> 1.18.2.1'         # TaurusxAdsSDK 1.18.2
end
pod install --repo-update

初始化

#import <CloudXCore/CloudXCore.h>

CLXInitializationConfiguration *config =
    [CLXInitializationConfiguration configurationWithAppKey:@"your-app-key-here"];

[[CloudXCore shared] initializeWithConfiguration:config completion:^(CLXSdkConfiguration *sdkConfig, CLXError * _Nullable error) {
    if (sdkConfig) {
        NSLog(@"CloudX SDK 初始化成功");
    } else {
        NSLog(@"CloudX SDK 初始化失败: %@", error.localizedDescription);
    }
}];

应用启动预热

链接 CloudXCore 后,SDK 会在应用启动时、您调用 initialize 之前预先完成部分工作。这是一项性能优化:它可以让 SDK 更快地完成初始化并加载首个广告,同时不会拖慢应用的启动速度。

预热不会占用应用的启动路径;除轻量的后台准备外,其余工作都会等到应用变为活跃状态后才执行。从未变为活跃状态的启动(例如后台获取或静默推送)只执行这部分轻量准备。应用扩展完全跳过预热。

这并不会初始化 SDK。您仍然需要调用 initialize,调用的时机和方式均无需改变。预热是尽力而为的:如果预热尚未完成,SDK 会在需要时自行完成这些工作,预热也不会导致应用启动失败。

如需关闭预热,请在应用的 Info.plist 中添加以下键:

Info.plist
<key>CloudXStartupWarmDisabled</key>
<true/>

两种方式的行为相同。设置该键后,SDK 会在您调用 initialize 时执行这些准备工作,因此首次初始化和首次广告加载会明显变慢。

广告格式

CloudX 支持横幅、MREC、插屏、激励、原生和 App Open 广告集成。请使用对应广告格式指南查看实现细节:

广告信息 (CLXAd)

CLXAd 对象会传递给代理回调,包含已加载/展示广告的信息:

属性类型描述
adFormatCLXAdFormat广告格式(横幅、MREC、插屏、激励、原生)
adUnitIdNSString?广告单元 ID
adUnitNameNSString?广告单元名称
networkNameNSString?获胜广告网络的名称
networkPlacementNSString?网络特定的展示位置 ID
placementNSString?通过 placement 属性设置的自定义展示位置
revenueNSNumber?展示级别收入(美元)
revenuePrecisionNSString?获胜广告网络提供的收入精度
creativeIdentifierNSString?用于素材级问题排查的素材标识符
requestLatencyNSTimeInterval从广告请求到广告响应的耗时(秒)
nativeAdCLXNativeAd?原生广告的素材容器;非原生格式为 nil
adValuesNSDictionary<NSString *, NSString *>SDK 为已加载广告提供的元数据;具体值可能因格式或网络而缺失

当需求方未返回素材 ID 时,creativeIdentifier 为 nil。它与您在 CLXRevenueData 中传入的 creativeId 不同,后者描述的是在 CloudX 广告流程之外竞得的展示。

- (void)didLoadAd:(CLXAd *)ad {
    NSLog(@"广告格式: %ld", (long)ad.adFormat);
    NSLog(@"网络: %@", ad.networkName);
    NSLog(@"收入: %@", ad.revenue);
}

错误处理

所有 SDK 错误都作为 CLXError 对象在代理回调中返回:

属性类型描述
codeCLXErrorCode错误类别
localizedDescriptionNSString人类可读的描述
underlyingErrorNSError?可选的底层错误

错误代码类别

范围类别常见代码
0通用CLXErrorCodeInternalError
100-199网络CLXErrorCodeNetworkError、CLXErrorCodeNetworkTimeout、CLXErrorCodeServerError、CLXErrorCodeNoConnection
200-299初始化CLXErrorCodeNotInitialized、CLXErrorCodeSDKDisabled、CLXErrorCodeNoAdaptersFound、CLXErrorCodeInvalidAppKey
300-399广告加载CLXErrorCodeNoFill、CLXErrorCodeInvalidAdUnit、CLXErrorCodeAdsDisabled
400-499展示CLXErrorCodeAdNotReady、CLXErrorCodeAdAlreadyShowing
600-699适配器CLXErrorCodeAdapterNoFill、CLXErrorCodeAdapterTimeout、CLXErrorCodeAdapterLoadTimeout、CLXErrorCodeAdapterInitializationError

高级功能

调试日志

[CloudXCore setMinLogLevel:CLXLogLevelDebug];  // 启用调试日志
[CloudXCore setMinLogLevel:CLXLogLevelNone];   // 禁用所有日志

日志级别: verbose < debug < info < warn < error < none

展示级别收入追踪

在任何广告格式上设置 revenueDelegate 以接收展示级别收入(ILR)回调。CLXAd 对象包含以美元计价的收入值和获胜网络名称。

self.bannerAd.revenueDelegate = self;

- (void)didPayRevenueForAd:(CLXAd *)ad {
    NSLog(@"收入: %@ 来自 %@", ad.revenue, ad.networkName);
}

适用于所有广告格式(横幅、MREC、插屏、激励、原生)。

将广告收入上报至 MMP

如需将 CloudX 广告收入转发至移动归因合作伙伴(MMP),请添加相应的连接器,而不要自行实现收入代理:Adjust、AppsFlyer 或 Singular。每个连接器都会在每次 CloudX 竞得的展示时上报一次,无需发布商编写胶水代码。在 CloudX 竞价中由 AppLovin MAX 或 Google 瀑布流赢得的展示不会被转发,因为这些 SDK 已自行向您的 MMP 上报该部分收入。

无需在 MMP 后台进行任何 CloudX 专属配置,但您的 MMP 账户需要启用广告收入上报。对于 Adjust,收入目前计入通用来源,而非 CloudX 标签。详见在 Adjust UI 中查看 CloudX 收入。

发布商上报收入数据

如果您的应用在 CloudX 广告流程之外接收来自 AdMob、InMobi、TopOn 或其他聚合平台的展示级收入回调或 bid 元数据,请在 CloudX 初始化完成后将这些事件转发给 CloudX:

字段必填描述
platform是Objective-C 中使用 CLXRevenuePlatformAdMob、CLXRevenuePlatformInMobi、CLXRevenuePlatformTopOn 或 CLXRevenuePlatformCustom(@"MyProvider");Swift 中使用 .adMob、.inMobi、.topOn 或 .custom("MyProvider")
revenue是单次展示收入,使用传入货币;不是 CPM/eCPM
adFormat是广告格式字符串,例如 banner、mrec、interstitial、rewarded、native 或 app_open
currencyCode否ISO 4217 货币代码(如已知)
precision否exact、estimated、publisherDefined 或 undefined
networkName否获胜广告网络名称(如已知)
adUnitId否聚合平台广告单元 ID
thirdPartyAdPlacementId否广告网络侧广告单元或 placement ID
creativeId否广告网络返回的素材 ID
networkPlacement否广告网络 placement 标识
countryCode否用户国家代码(如已知)
userSegment否用户分群(如已知)

当事件被 CloudX 收益链路接受时,reportRevenueData(_:) 返回 true。如果 SDK 尚未初始化、服务端未启用收益跟踪,或平台名称为空,则返回 false。接受事件不保证一定送达。

AdMob paid event

AdMob iOS 的 AdValue.value 已经是所提供货币的正常单位,因此请直接传给 CloudX。不要除以 1_000_000.0。

static CLXRevenuePrecision *CLXRevenuePrecisionFromGAD(GADAdValuePrecision precision) {
    switch (precision) {
        case GADAdValuePrecisionPrecise: return CLXRevenuePrecision.exact;
        case GADAdValuePrecisionEstimated: return CLXRevenuePrecision.estimated;
        case GADAdValuePrecisionPublisherProvided: return CLXRevenuePrecision.publisherDefined;
        case GADAdValuePrecisionUnknown: return CLXRevenuePrecision.undefined;
    }
    return CLXRevenuePrecision.undefined;
}

- (BOOL)reportAdMobPaidEventWithAdValue:(GADAdValue *)adValue
                               adFormat:(NSString *)adFormat
                                adUnitId:(NSString *)adUnitId
                            responseInfo:(GADResponseInfo *)responseInfo {
    GADAdNetworkResponseInfo *servedBy = responseInfo.loadedAdNetworkResponseInfo;
    CLXRevenueData *data =
        [CLXRevenueData revenueDataWithPlatform:CLXRevenuePlatformAdMob
                                        revenue:adValue.value.doubleValue
                                       adFormat:adFormat
                                   builderBlock:^(CLXRevenueDataBuilder *builder) {
            builder.currencyCode = adValue.currencyCode;
            builder.precision = CLXRevenuePrecisionFromGAD(adValue.precision);
            builder.networkName = servedBy.adSourceName;
            builder.adUnitId = adUnitId;
            builder.thirdPartyAdPlacementId = servedBy.adSourceInstanceName;
        }];

    return [[CloudXCore shared] reportRevenueData:data];
}

__weak GADBannerView *weakBannerView = bannerView;
bannerView.paidEventHandler = ^(GADAdValue *adValue) {
    [self reportAdMobPaidEventWithAdValue:adValue
                                 adFormat:@"banner"
                                  adUnitId:adUnitId
                              responseInfo:weakBannerView.responseInfo];
};

InMobi impression event

对于 InMobi,请在 banner(_:didReceiveWithMetaInfo:) 中保存 IMAdMetaInfo。当 bannerAdImpressed(_:) 触发时,把保存的 metaInfo.getBid() 上报给 CloudX,然后清空保存的值。

@property (nonatomic, strong, nullable) IMAdMetaInfo *latestInMobiMetaInfo;

- (void)banner:(IMBanner *)banner didReceiveWithMetaInfo:(IMAdMetaInfo *)info {
    self.latestInMobiMetaInfo = info;
}

- (void)bannerAdImpressed:(IMBanner *)banner {
    if (!self.latestInMobiMetaInfo) {
        return;
    }

    [self reportInMobiImpressionWithMetaInfo:self.latestInMobiMetaInfo
                                    adFormat:@"banner"
                                 placementId:inMobiPlacementId];
    self.latestInMobiMetaInfo = nil;
}

- (BOOL)reportInMobiImpressionWithMetaInfo:(IMAdMetaInfo *)metaInfo
                                  adFormat:(NSString *)adFormat
                               placementId:(NSString *)placementId {
    CLXRevenueData *data =
        [CLXRevenueData revenueDataWithPlatform:CLXRevenuePlatformInMobi
                                        revenue:[metaInfo getBid]
                                       adFormat:adFormat
                                   builderBlock:^(CLXRevenueDataBuilder *builder) {
            builder.precision = CLXRevenuePrecision.estimated;
            builder.thirdPartyAdPlacementId = placementId;
            builder.creativeId = metaInfo.creativeID;
        }];

    return [[CloudXCore shared] reportRevenueData:data];
}

对于 InMobi 插屏和激励视频广告,请在 IMInterstitialDelegate 中使用相同模式:在 interstitial(_:didReceiveWithMetaInfo:) 中保存 IMAdMetaInfo,然后在 interstitialAdImpressed(_:) 中上报。

TopOn revenue event

TopOn iOS 会在 didRevenueForPlacementID:extra: 中提供收入。请使用 extra 中的 publisher_revenue 作为单次展示收入,并使用 currency 作为货币代码。

static NSString *CLXTopOnStringValue(NSDictionary *extra, NSString *key) {
    id value = extra[key];
    return [value isKindOfClass:NSString.class] ? value : nil;
}

static CLXRevenuePrecision *CLXRevenuePrecisionFromTopOn(NSString *precision) {
    if ([precision isEqualToString:@"exact"]) return CLXRevenuePrecision.exact;
    if ([precision isEqualToString:@"estimated"]) return CLXRevenuePrecision.estimated;
    if ([precision isEqualToString:@"publisher_defined"]) return CLXRevenuePrecision.publisherDefined;
    return CLXRevenuePrecision.undefined;
}

- (BOOL)reportTopOnRevenueForPlacementID:(NSString *)placementID
                                   extra:(NSDictionary *)extra
                                adFormat:(NSString *)adFormat {
    NSNumber *revenue = extra[@"publisher_revenue"];
    if (![revenue isKindOfClass:NSNumber.class]) {
        return NO;
    }

    CLXRevenueData *data =
        [CLXRevenueData revenueDataWithPlatform:CLXRevenuePlatformTopOn
                                        revenue:revenue.doubleValue
                                       adFormat:adFormat
                                   builderBlock:^(CLXRevenueDataBuilder *builder) {
            builder.currencyCode = CLXTopOnStringValue(extra, @"currency");
            builder.precision = CLXRevenuePrecisionFromTopOn(CLXTopOnStringValue(extra, @"precision"));
            builder.networkName = CLXTopOnStringValue(extra, @"network_name");
            builder.adUnitId = placementID;
            builder.thirdPartyAdPlacementId = CLXTopOnStringValue(extra, @"network_placement_id");
            builder.networkPlacement = CLXTopOnStringValue(extra, @"adsource_id");
            builder.countryCode = CLXTopOnStringValue(extra, @"country");
        }];

    return [[CloudXCore shared] reportRevenueData:data];
}

- (void)didRevenueForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
    [self reportTopOnRevenueForPlacementID:placementID
                                     extra:extra
                                  adFormat:@"banner"];
}

自定义平台事件

对于没有 CloudX SDK 内置常量的 provider,请使用 Custom;AdMob、InMobi 和 TopOn 已有内置常量。这里的值只表示 provider 名称,例如 Objective-C 中的 CLXRevenuePlatformCustom(@"TradPlus") 或 Swift 中的 CLXRevenuePlatform.custom("TradPlus")。请保持名称稳定,便于 CloudX 一致归类该 provider 的收益。

不要把金额或币种放进 provider 名称里。如果一条 TradPlus 展示带来 USD 0.01 收入,请传 platform TradPlus、revenue 0.01 和 currency code USD。如果来源返回 CPM/eCPM,请先除以 1_000.0。

- (BOOL)reportCustomRevenueEventWithProviderName:(NSString *)providerName
                                         revenue:(double)revenue
                                        adFormat:(NSString *)adFormat
                                    currencyCode:(NSString *)currencyCode
                                        adUnitId:(NSString *)adUnitId
                                     placementId:(NSString *)placementId {
    CLXRevenueData *data =
        [CLXRevenueData revenueDataWithPlatform:CLXRevenuePlatformCustom(providerName)
                                        revenue:revenue
                                       adFormat:adFormat
                                   builderBlock:^(CLXRevenueDataBuilder *builder) {
            builder.currencyCode = currencyCode;
            builder.precision = CLXRevenuePrecision.publisherDefined;
            builder.adUnitId = adUnitId;
            builder.thirdPartyAdPlacementId = placementId;
        }];

    return [[CloudXCore shared] reportRevenueData:data];
}

代理回调线程

发布方代理回调会在主队列上派发,并且可能相对于触发它们的 SDK 调用以内联方式触发。如果代理方法会再次调用 SDK,请确保处理逻辑可重入。

测试模式

测试模式通过设备白名单进行服务端控制。这提供了更好的安全性和对哪些设备接收测试广告的控制。

启用测试模式:

  1. 初始化 SDK 并检查日志中的设备 IFA:

    [CloudX][INFO] Device IFA for test whitelisting: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
    
  2. 复制 IFA 并将其添加到 CloudX 服务器仪表板的设备白名单中

  3. SDK 将自动为测试模式配置适配器并在竞价请求中包含测试标志

隐私合规

CloudX SDK 通过从 NSUserDefaults 读取标准 IAB 隐私字符串来支持 GDPR 和 CCPA 隐私合规。这些值通常由您的同意管理平台(CMP)自动设置,如 Google UMP、OneTrust 或 Sourcepoint。

工作原理

SDK 自动检测用户位置并读取同意信号:

  1. 欧盟用户(GDPR):根据 IAB 全球供应商列表 检查 TCF v2 对目的 1 和 2 的同意和供应商同意(CloudX 供应商 ID:1510)
  2. 美国用户(CCPA):检查销售/共享选择退出信号
  3. 其他地区:无限制

当用户拒绝同意或选择退出时,SDK 会从广告请求中移除 PII:

  • 广告标识符(IDFA)被清除
  • 地理坐标(纬度/经度)被移除
  • 用户键值不会发送
  • 哈希用户 ID 会从发送给竞价方的请求中移除,并且不会写入展示级报表

支持的隐私密钥

密钥标准描述
IABGPP_HDR_GppStringGPP全球隐私平台字符串(现代)
IABGPP_GppSIDGPPSection IDs(如 “2” 为欧盟,“7” 为美国国家,“8” 为美国加州)
IABTCF_TCStringTCF v2GDPR 同意字符串(旧版)
IABTCF_gdprAppliesTCF v2GDPR 是否适用(1 = 是,0 = 否)
IABUSPrivacy_StringUS PrivacyCCPA 隐私字符串(旧版,如 “1YNN”)

应用追踪透明度(ATT)

在 iOS 14.5+ 上,您必须在 SDK 可以访问 IDFA 之前请求应用追踪透明度授权。在初始化 CloudX SDK 之前请求 ATT 权限:

#import <AppTrackingTransparency/AppTrackingTransparency.h>

if (@available(iOS 14.5, *)) {
    [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
        // 在 ATT 响应后初始化 CloudX SDK
        [self initializeCloudX];
    }];
} else {
    [self initializeCloudX];
}

在 Info.plist 中添加 NSUserTrackingUsageDescription 键,并说明您需要追踪权限的原因。

手动隐私 API

如果您自行管理用户同意(不使用 CMP),可以直接设置 GDPR 和 CCPA 隐私状态。请在初始化 SDK 之前调用这些方法 — 部分广告网络 SDK 要求在初始化时设置隐私参数,初始化后设置的值可能不会生效。

// 在初始化 SDK 之前设置隐私
[CloudXCore setHasUserConsent:@YES];
[CloudXCore setDoNotSell:@NO];

[[CloudXCore shared] initializeWithConfiguration:config completion:completion];

用户定向

// 设置哈希用户 ID 用于定向
[[CloudXCore shared] setHashedUserID:@"hashed-user-id"];

// 设置自定义用户键值对(受隐私法规清除)
[[CloudXCore shared] setUserKeyValue:@"age" value:@"25"];
[[CloudXCore shared] setUserKeyValue:@"gender" value:@"male"];
[[CloudXCore shared] setUserKeyValue:@"location" value:@"US"];

// 设置用于请求定向的自定义应用键值对
[[CloudXCore shared] setAppKeyValue:@"app_version" value:@"1.0.0"];
[[CloudXCore shared] setAppKeyValue:@"user_level" value:@"premium"];

// 清除所有自定义键值
[[CloudXCore shared] clearAllKeyValues];

哈希用户 ID 是由发布商提供的假名标识符。当适用的隐私信号允许时,CloudX 会在请求和展示活动导出中以 hashed_user_id 返回竞价时记录的值。使用 auction_id 关联这些导出。您可以使用该 ID 将 CloudX 活动与自己的用户数据分群关联。未设置 ID、隐私信号阻止持久化或值超过 128 个字符时,导出值为空。请勿传递未经哈希的个人数据。

用户和应用键值对也会出现在请求活动导出中。使用 setUserKeyValue 设置 SDK 会话范围内的用户属性,使用 setAppKeyValue 设置 SDK 会话范围内的应用属性。SDK 会在之后的每个竞价请求中发送当前值。

身份透传

通过 setUserKeyValue 传入 UID 2.0、EUID、LiveRamp、ID5 和 Intent IQ。有值时设置一次,刷新后再设一次。

键传入内容
uidapi.comUID2 广告 token,不要传 refresh token。不要解密。UID2 文档
euid.euEUID 广告 token,不要传 refresh token。不要解密。EUID 文档
liveramp.comLiveRamp ATS 信封,不要传 RampID。LiveRamp 文档
id5-sync.comID5 通用 UID。不要传 0。ID5 文档
intentiq.comIntent IQ ID(IIQ ID),来自你的 Intent IQ 集成。Intent IQ 文档

CloudX 不会生成这些 ID。请先用 UID2、EUID、LiveRamp ATS、ID5 或 Intent IQ 生成,再把字符串传进来。这与哈希用户 ID 不是同一回事。

CloudXCore.shared.setUserKeyValue("uidapi.com", value: uid2Token)
CloudXCore.shared.setUserKeyValue("euid.eu", value: euidToken)
CloudXCore.shared.setUserKeyValue("liveramp.com", value: liveRampEnvelope)
CloudXCore.shared.setUserKeyValue("id5-sync.com", value: id5Id)
CloudXCore.shared.setUserKeyValue("intentiq.com", value: intentIQId)

如需为单个广告对象或加载添加元数据,请在调用 load 前使用 setExtraParameter:

banner?.setExtraParameter("requestId", value: "request-456")
banner?.setExtraParameter("impressionKey", value: "impression-789")
banner?.load()

额外参数会保留在该广告对象上,直到被修改或清除。SDK 会为每次加载创建参数快照。CloudX 会在请求导出的 extra_parameters 列中返回完整参数对象。保留的 tags 键还会控制基于标签的路由;请使用其他键存储关联元数据。

只有当整个紧凑 JSON 对象不超过 256 个 UTF-8 字节时,CloudX 才会存储对应的参数对象。每个导出列分别适用该限制,且计数包含所有键、值、引号、分隔符和大括号的合计大小,并非每个键值对可各用 256 字节。如果隐私规则禁止持久化发布商数据,或者对象格式错误或过大,则对应导出单元格为空。请勿包含原始个人数据、密钥或同意字符串。

技术支持

如需支持,请联系 support@cloudx.io