概览

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

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

安装

CocoaPods

Podfile
platform :ios, '13.0'

target 'YourApp' do
  use_frameworks!

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

  # 广告网络适配器(根据需要添加)。
  # 每个适配器独立按 <网络 SDK 版本>.<适配器修订号> 版本化,
  # 并针对该确切网络 SDK 版本构建。
  pod 'CloudXMetaAdapter', '~> 6.22.0.0'            # FBAudienceNetwork 6.22.0
  pod 'CloudXVungleAdapter', '~> 7.7.4.0'           # VungleAds 7.7.4
  pod 'CloudXInMobiAdapter', '~> 11.3.0.0'          # InMobiSDK 11.3.0
  pod 'CloudXMintegralAdapter', '~> 8.1.5.0'        # MintegralAdSDK 8.1.5
  pod 'CloudXUnityAdsAdapter', '~> 4.19.0.0'        # UnityAds 4.19.0
  pod 'CloudXMagniteAdapterV2', '~> 1.0.0.1'        # MagniteSDK 1.0.0
  pod 'CloudXMobileFuseAdapter', '~> 1.11.0.0'      # MobileFuseSDK 1.11.0
  pod 'CloudXMolocoAdapter', '~> 4.8.0.0'           # MolocoSDKiOS 4.8.0
  pod 'CloudXVerveAdapter', '~> 3.9.0.0'            # HyBid 3.9.0
  pod 'CloudXDigitalTurbineAdapter', '~> 8.4.8.0'   # Fyber Marketplace SDK 8.4.8
  pod 'CloudXGoogleWaterfallAdapter', '~> 13.6.0.3' # Google Mobile Ads SDK 13.6.0
  pod 'CloudXPangleAdapter', '~> 7.9.1.3.0'         # Ads-Global(Pangle / 字节跳动)7.9.1.3
  pod 'CloudXTaurusXAdapter', '~> 1.18.1.0'         # TaurusxAdsSDK 1.18.1
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);
    }
}];

广告格式

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 为已加载广告提供的元数据;具体值可能因格式或网络而缺失
- (void)didLoadAd:(CLXAd *)ad {
    NSLog(@"广告格式: %ld", (long)ad.adFormat);
    NSLog(@"网络: %@", ad.networkName);
    NSLog(@"收入: %@", ad.revenue);
}

错误处理

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

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

错误代码类别

范围类别常见代码
0通用CLXErrorCodeInternalError
100-199网络CLXErrorCodeNetworkErrorCLXErrorCodeNetworkTimeoutCLXErrorCodeServerErrorCLXErrorCodeNoConnection
200-299初始化CLXErrorCodeNotInitializedCLXErrorCodeSDKDisabledCLXErrorCodeNoAdaptersFoundCLXErrorCodeInvalidAppKey
300-399广告加载CLXErrorCodeNoFillCLXErrorCodeInvalidAdUnitCLXErrorCodeAdsDisabled
400-499展示CLXErrorCodeAdNotReadyCLXErrorCodeAdAlreadyShowing
600-699适配器CLXErrorCodeAdapterNoFillCLXErrorCodeAdapterTimeoutCLXErrorCodeAdapterLoadTimeoutCLXErrorCodeAdapterInitializationError

高级功能

调试日志

[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、插屏、激励、原生)。

发布商上报收入数据

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

字段必填描述
platformObjective-C 中使用 CLXRevenuePlatformAdMobCLXRevenuePlatformInMobiCLXRevenuePlatformTopOnCLXRevenuePlatformCustom(@"MyProvider");Swift 中使用 .adMob.inMobi.topOn.custom("MyProvider")
revenue单次展示收入,使用传入货币;不是 CPM/eCPM
adFormat广告格式字符串,例如 bannermrecinterstitialrewardednativeapp_open
currencyCodeISO 4217 货币代码(如已知)
precisionexactestimatedpublisherDefinedundefined
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 返回。您可以使用该值将 CloudX 收入与自己的用户数据分群关联。未设置 ID、隐私信号阻止持久化或值超过 128 个字符时,导出值为空。请勿传递未经哈希的个人数据。

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

身份透传

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

传入内容
uidapi.comUID2 广告 token,不要传 refresh token。不要解密。UID2 文档
euid.euEUID 广告 token,不要传 refresh token。不要解密。EUID 文档
liveramp.comLiveRamp ATS 信封,不要传 RampID。LiveRamp 文档
id5-sync.comID5 通用 UID。不要传 0ID5 文档

CloudX 不会生成这些 ID。请先用 UID2、EUID、LiveRamp ATS 或 ID5 生成,再把字符串传进来。这与哈希用户 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)

如需为单个广告对象或加载添加元数据,请在调用 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