Skip to content

iOS SDK 集成

概述

优聚智汇 iOS 广告聚合 SDK 采用与 Android 对齐的设计:

  • 统一入口UjuAdSdk.shared 单例
  • 两阶段初始化init(config:) 轻量配置 + start(listener:) 正式启动
  • 工厂方法模式UjuAdSdk.shared.createXxxAd(viewController:config:) 创建统一广告对象 UjuAdObjectProtocol
  • 统一监听器体系BaseAdObjectListener + 各广告类型子监听器
  • 内置 ADX:自有广告源已内置,无需手动集成

iOS SDK 当前处于开发推进阶段,正式版本即将发布。本文档基于 SDK 源码(ios/Sources/UjuAdSdk/Public/)事实编写。

SDK 初始化

iOS SDK 采用两阶段初始化以满足隐私合规:init 阶段仅保存配置、不采集任何隐私信息;start 阶段才采集设备信息、拉取策略。

1. 构造初始化配置

swift
import UjuAdSdk

// 构造初始化配置(appId / appKey 必填,precondition 校验非空)
let config = UjuAdInitConfig(
    appId: "YOUR_APP_ID",        // 必填,优聚智慧后台分配的应用 ID
    appKey: "YOUR_APP_KEY",      // 必填,优聚智慧后台分配的 RSA 公钥
    channel: "AppStore",         // 渠道标识,用于数据统计与分账
    subChannel: "",              // 子渠道(可选)
    isDebug: true,               // 调试模式,发布前请改为 false
    wxAppId: "YOUR_WX_APP_ID"    // 微信 AppId(可选,用于 Deeplink 归因)
)

2. 两阶段初始化

AppDelegate 中完成初始化:

swift
import UIKit
import UjuAdSdk

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 步骤 1:构造配置
        let config = UjuAdInitConfig(
            appId: "YOUR_APP_ID",
            appKey: "YOUR_APP_KEY",
            channel: "AppStore",
            isDebug: true
        )

        // 步骤 2:init(轻量,不采集隐私信息)
        UjuAdSdk.shared.init(config: config)

        // 步骤 3:(可选)注册三方适配器工厂
        // UjuAdSdk.shared.registerAdapterFactory(CsjAdapterFactory())

        // 步骤 4:start(正式启动,采集设备信息、拉取策略)
        UjuAdSdk.shared.start(listener: { result in
            switch result {
            case .success:
                print("优聚智汇 SDK 启动成功,可以开始加载广告")
            case .failure(let error):
                print("优聚智汇 SDK 启动失败: \(error.message)")
            }
        })

        return true
    }
}

BaseInitListener 协议提供 onInitSuccess()onInitFailed(_ error: UjuException) 两个回调,可按需实现。

3. UjuAdInitConfig 字段说明

字段类型必填默认值说明
appIdString-优聚智慧后台分配的应用 ID
appKeyString-优聚智慧后台分配的 RSA 公钥(非普通密钥)
channelString""渠道标识,用于数据统计与分账
subChannelString""子渠道
isDebugBoolfalse调试模式,发布前请设为 false
wxAppIdString""微信 AppId,用于 Deeplink 归因
privacyConfigUjuPrivacyConfig?nil隐私合规配置
personalizationUjuPersonalizedConfig?nil个性化用户信息
presetStrategyFileNameString""离线策略文件名(placement_config.json

4. 个性化配置 UjuPersonalizedConfig

swift
let personalization = UjuPersonalizedConfig(
    userId: "USER_123",    // 必填,用户 ID
    userAge: 25,           // 必填,用户年龄
    userGender: 1,         // 1=男 / 2=女 / 0=未知
    userKeywords: ["game", "news"]  // 用户兴趣关键词
)
let config = UjuAdInitConfig(
    appId: "YOUR_APP_ID",
    appKey: "YOUR_APP_KEY",
    channel: "AppStore",
    personalization: personalization
)

请在获得用户明确同意后再传入个性化信息。

5. 隐私配置 UjuPrivacyConfig

字段类型默认值说明
useLocationBooltrue是否使用位置信息
useAppListBooltrue是否使用已安装应用列表
usePhoneStateBooltrue是否使用设备状态
useWifiStateBooltrue是否使用 WiFi 状态
imeiString?nil开发者自定义 IMEI
idfaString?nil开发者自定义 IDFA
idfvString?nil开发者自定义 IDFV
macAddressString?nil开发者自定义 MAC

广告加载通用流程

所有广告类型遵循统一流程:

  1. 构造 UjuAdConfig(placementId:...)
  2. 调用 UjuAdSdk.shared.createXxxAd(viewController:config:) 获取 UjuAdObjectProtocol
  3. setAdObjectListener(_:) 设置监听器
  4. load() 加载广告
  5. onLoadSuccess 回调后,调用 show()(激励/插屏)或 show(in: container)(开屏/横幅/原生)
  6. 不再使用时调用 destroy()

UjuAdConfig 字段说明

字段类型必填默认值说明
placementIdString-广告位 ID(precondition 校验非空)
scenarioKeyString""广告展示场景标识,用于统计
adViewSizeAdViewSizeAdViewSize()广告尺寸,仅对模板信息流(FeedType.express)生效
adFormatAdFormat.native广告格式(工厂方法会自动覆盖)
userIdString?nil用户 ID,激励视频常用
customData[String: String][:]自定义数据,透传给广告服务端

监听器签名说明

iOS SDK 监听器与 Android 对齐,需特别注意加载失败展示失败的参数数量差异:

回调参数数量所属协议触发阶段
onLoadSuccess(_ placementId: String)1BaseAdObjectListener加载成功
onLoadError(_ error: UjuException, placementId: String)2BaseAdObjectListener加载失败
onAdError(_ error: UjuException)1各子监听器展示阶段失败

所有协议均有默认空实现(public extension),调用方可只实现关心的回调。

广告类型集成

1. 开屏广告

swift
import UjuAdSdk

class SplashAdHelper {
    private var splashAd: UjuAdObjectProtocol?

    /// 加载开屏广告
    /// - Parameter viewController: 用于展示开屏的 ViewController
    /// - Parameter container: 承载开屏广告视图的容器
    func load(viewController: UIViewController, container: UIView) {
        // 构造广告配置
        let config = UjuAdConfig(placementId: "YOUR_SPLASH_PLACEMENT_ID")
        // 通过工厂方法创建开屏广告对象
        splashAd = UjuAdSdk.shared.createSplashAd(viewController: viewController, config: config)

        // 设置监听器
        splashAd?.setAdObjectListener(SplashAdObjectListenerWrapper(
            onLoadSuccess: { [weak self] placementId in
                // 加载成功,展示广告
                self?.splashAd?.show(in: container)
            },
            onLoadError: { error, placementId in
                // 加载失败(2 参数:error + placementId)
                print("Splash onLoadError: code=\(error.code), msg=\(error.message), pid=\(placementId)")
            },
            onAdShow: { print("Splash onAdShow") },
            onAdClicked: { print("Splash onAdClicked") },
            onAdClosed: { print("Splash onAdClosed,进入主界面") },
            onAdError: { error in
                // 展示阶段失败(1 参数:error)
                print("Splash onAdError: code=\(error.code), msg=\(error.message)")
            }
        ))

        // 开始加载
        splashAd?.load()
    }

    func destroy() {
        splashAd?.destroy()
        splashAd = nil
    }
}

2. 激励视频广告

swift
import UjuAdSdk

class RewardAdHelper {
    private var rewardAd: UjuAdObjectProtocol?

    /// 加载激励视频广告
    func load(viewController: UIViewController) {
        // 激励视频建议传入 userId
        let config = UjuAdConfig(
            placementId: "YOUR_REWARD_PLACEMENT_ID",
            userId: "USER_123"
        )
        rewardAd = UjuAdSdk.shared.createRewardAd(viewController: viewController, config: config)

        rewardAd?.setAdObjectListener(RewardAdObjectListenerWrapper(
            onLoadSuccess: { [weak self] placementId in
                // 加载成功,展示广告
                self?.rewardAd?.show()
            },
            onLoadError: { error, placementId in
                print("Reward onLoadError: code=\(error.code), msg=\(error.message)")
            },
            onAdShow: { print("Reward onAdShow") },
            onAdClicked: { print("Reward onAdClicked") },
            onAdClosed: { print("Reward onAdClosed") },
            onReward: { rewardType, rewardAmount in
                // 用户获得奖励(2 参数:rewardType + rewardAmount)
                print("Reward onReward: type=\(rewardType ?? ""), amount=\(rewardAmount)")
                // 在此发放开发者预定义的奖励
            },
            onVideoComplete: { print("Reward onVideoComplete") },
            onAdError: { error in
                print("Reward onAdError: code=\(error.code), msg=\(error.message)")
            }
        ))

        rewardAd?.load()
    }

    func destroy() {
        rewardAd?.destroy()
        rewardAd = nil
    }
}

激励视频的 onReward(rewardType:rewardAmount:)2 参数回调,rewardType 可能为 nil,奖励发放逻辑由开发者自定义。

3. 插屏广告

swift
import UjuAdSdk

class InterstitialAdHelper {
    private var interstitialAd: UjuAdObjectProtocol?

    func load(viewController: UIViewController) {
        let config = UjuAdConfig(placementId: "YOUR_INTERSTITIAL_PLACEMENT_ID")
        interstitialAd = UjuAdSdk.shared.createInterstitialAd(viewController: viewController, config: config)

        interstitialAd?.setAdObjectListener(InterstitialAdObjectListenerWrapper(
            onLoadSuccess: { [weak self] placementId in
                self?.interstitialAd?.show()
            },
            onLoadError: { error, placementId in
                print("Interstitial onLoadError: code=\(error.code), msg=\(error.message)")
            },
            onAdShow: { print("Interstitial onAdShow") },
            onAdClicked: { print("Interstitial onAdClicked") },
            onAdClosed: { print("Interstitial onAdClosed") },
            onAdError: { error in
                print("Interstitial onAdError: code=\(error.code), msg=\(error.message)")
            }
        ))

        interstitialAd?.load()
    }

    func destroy() {
        interstitialAd?.destroy()
        interstitialAd = nil
    }
}

4. 横幅广告

swift
import UjuAdSdk

class BannerAdHelper {
    private var bannerAd: UjuAdObjectProtocol?

    /// 加载横幅广告
    /// - Parameter container: 承载横幅广告视图的容器
    func load(viewController: UIViewController, container: UIView) {
        let config = UjuAdConfig(
            placementId: "YOUR_BANNER_PLACEMENT_ID",
            adViewSize: AdViewSize.bannerDefault  // 320x50
        )
        bannerAd = UjuAdSdk.shared.createBannerAd(viewController: viewController, config: config)

        bannerAd?.setAdObjectListener(FeedAdObjectListenerWrapper(
            onLoadSuccess: { [weak self] placementId in
                // 加载成功,展示广告
                self?.bannerAd?.show(in: container)
            },
            onLoadError: { error, placementId in
                print("Banner onLoadError: code=\(error.code), msg=\(error.message)")
            },
            onAdShow: {
                // 通过 getAdInfo() 获取广告信息,直接读取 ecpm
                let ecpm = self.bannerAd?.getAdInfo()?.ecpm
                print("Banner onAdShow, ecpm: \(ecpm ?? 0)")
            },
            onAdClicked: { print("Banner onAdClicked") },
            onAdClosed: { print("Banner onAdClosed") },
            onAdError: { error in
                print("Banner onAdError: code=\(error.code), msg=\(error.message)")
            }
        ))

        bannerAd?.load()
    }

    func destroy() {
        bannerAd?.destroy()
        bannerAd = nil
    }
}

5. 原生广告

原生广告支持模板渲染FeedType.express)与自渲染FeedType.unified)两种方式。自渲染需通过 NativeAdViewBinder 绑定视图(iOS 使用 view.tag 标识组件,与 Android 的 resourceId 不同)。

swift
import UjuAdSdk

class NativeAdHelper {
    private var nativeAd: UjuAdObjectProtocol?

    /// 加载原生广告
    func load(viewController: UIViewController, container: UIView) {
        let config = UjuAdConfig(
            placementId: "YOUR_NATIVE_PLACEMENT_ID",
            adViewSize: AdViewSize.nativeDefault  // 模板信息流尺寸
        )
        nativeAd = UjuAdSdk.shared.createNativeAd(viewController: viewController, config: config)

        nativeAd?.setAdObjectListener(FeedAdObjectListenerWrapper(
            onLoadSuccess: { [weak self] placementId in
                guard let self = self, let ad = self.nativeAd else { return }
                if ad.getFeedType() == .express {
                    // 模板渲染:直接 show
                    ad.show(in: container)
                } else {
                    // 自渲染:获取物料数据并绑定视图
                    self.renderNativeAd(ad: ad, container: container)
                }
            },
            onLoadError: { error, placementId in
                print("Native onLoadError: code=\(error.code), msg=\(error.message)")
            },
            onAdShow: { print("Native onAdShow") },
            onAdClicked: { print("Native onAdClicked") },
            onAdClosed: { print("Native onAdClosed") },
            onAdError: { error in
                print("Native onAdError: code=\(error.code), msg=\(error.message)")
            }
        ))

        nativeAd?.load()
    }

    /// 自渲染原生广告:使用 NativeAdViewBinder 绑定视图
    private func renderNativeAd(ad: UjuAdObjectProtocol, container: UIView) {
        // 获取物料数据
        guard let data = ad.getAdData() else { return }

        // 准备广告视图(通过 view.tag 标识各组件)
        let adView = UIView()
        // adView.subviews 通过 tag 关联到 binder 的 *Tag 字段

        // 创建视图绑定器(iOS 使用 view.tag 标识组件)
        let binder = NativeAdViewBinder(
            titleTag: 1001,        // 标题视图 tag
            descTag: 1002,         // 描述视图 tag
            iconTag: 1003,         // 图标视图 tag
            imageTag: 1004,        // 主图视图 tag
            ctaTag: 1005,          // 行动号召按钮 tag
            sourceTag: 1006,       // 来源视图 tag(可选)
            mediaViewTag: 1007,    // 媒体视图 tag(视频广告,可选)
            logoTag: 1008,         // 广告标识 tag(可选)
            clickViewTags: [1009], // 额外可点击视图 tag 列表(可选)
            dislikeTag: 1010       // 不感兴趣按钮 tag(可选)
        )

        // 注册交互视图并展示
        ad.registerViewForInteraction(view: adView, container: container, binder: binder)
        ad.show(in: container)
    }

    func destroy() {
        nativeAd?.destroy()
        nativeAd = nil
    }
}

UjuAdSdk 公共方法

方法返回值说明
sharedUjuAdSdk单例入口
init(config: UjuAdInitConfig)Void第一阶段:配置初始化(不采集隐私)
start(listener: BaseInitListener?)Void第二阶段:正式启动 SDK
isSdkInitialized()Bool是否已调用 init
getInitializeState()UjuAdInitStatus获取初始化状态枚举
getVersion()String获取 SDK 版本号
getAppId()String获取应用 ID
getOAID()StringiOS 对应 IDFA,未获取返回空串
getGoogleAdId()String获取 GoogleAdId
updateChannel(_ channel: String, subChannel: String)Void更新渠道
registerAdapterFactory(_ factory: BaseAdapterFactory)Bool注册单个适配器工厂
registerAdapterFactories(_ factories: [BaseAdapterFactory])Void批量注册
getRegisteredPlatforms()[PlatformInstanceKey]查询已注册平台
destroy()Void释放 SDK 资源

枚举参考

AdFormat(广告格式)

枚举说明
1native原生信息流
2reward激励视频
3banner横幅
4interstitial插屏
5splash开屏
6videoStream视频流

FeedType(信息流渲染类型)

枚举说明
1express模板渲染
2unified自渲染

AdMediaType(自渲染媒体类型)

枚举说明
1singleImage单图
2multiImage多图
3video视频

AdBidType(竞价类型)

枚举说明
1normal瀑布流
2adxADX 竞价
3c2s客户端竞价
4s2s服务端竞价

AdPlatformType(广告平台)

枚举说明
1csj穿山甲
2ylh优量汇
3bd百度
4ks快手
6oppoOPPO
7vivoVIVO
100adx优聚内部 ADX

UjuAdInitStatus(初始化状态)

枚举说明
0idle未初始化
1initializing初始化中
2initialized已初始化
3initializationFailed初始化失败

错误码(iOS ErrorType)

iOS SDK 错误码定义在 ErrorType 枚举中,与 Android 错误码不同,请勿混淆:

错误码枚举含义
1001initError初始化错误
1002networkError网络错误
1003configError配置错误
1004loadError加载错误
1005biddingError竞价错误
1006notFindAdapter未找到适配器
1007adShowError展示错误
1008adClickError点击错误
9999unknown未知错误

错误对象为 UjuExceptionpublic struct: Error),含 type: ErrorTypecode: Intmessage: String

NativeAdData 字段

UjuAdObjectProtocol.getAdData() 返回 NativeAdData,用于自渲染填充数据:

字段类型说明
titleString?广告标题
descString?广告描述
sourceString?广告来源
iconUrlString?广告图标 URL
mediaTypeAdMediaType媒体类型(默认 .singleImage
imageUrlString?单图 URL
imageUrlList[String]?多图 URL 列表
callToActionString?行动号召文案
videoUrlString?视频 URL
clickUrlString?点击 URL

NativeAdViewBinder 字段

NativeAdViewBinder 用于自渲染绑定视图,iOS 通过 view.tag 标识组件(与 Android 的 resourceId 不同):

字段类型必填说明
titleTagInt标题视图 tag
descTagInt描述视图 tag
iconTagInt图标视图 tag
imageTagInt主图视图 tag
ctaTagInt行动号召按钮 tag
sourceTagInt?来源视图 tag
mediaViewTagInt?媒体视图 tag(视频广告)
logoTagInt?广告标识 tag
clickViewTags[Int]?额外可点击视图 tag 列表
dislikeTagInt?不感兴趣按钮 tag

UjuAdInfo 字段

UjuAdObjectProtocol.getAdInfo() 返回 UjuAdInfo,包含广告元数据:

字段类型说明
ecpmDouble广告出价(eCPM,应用 revenueRate 后)
placementIdString广告位 ID
soltIdString广告代码位 ID(注:源码拼写为 soltId
platformIdInt广告平台 ID(参见 AdPlatformType
adFormatAdFormat广告格式
originalDoubleDataDouble原始 eCPM(未转换)

生命周期与销毁

  • 每个 UjuAdObjectProtocol 不再使用时必须调用 destroy() 释放资源
  • 应用退出时调用 UjuAdSdk.shared.destroy() 释放 SDK 资源
  • destroy() 后不要再调用任何方法或触发展示上报

常见问题

Q: SDK 初始化失败怎么办?

A: 检查以下几点:

  • appIdappKey(RSA 公钥)是否正确
  • appKey 是否原样传入(不要截断或修改)
  • 网络连接是否正常
  • isSdkInitialized() 是否返回 true

Q: IDFA 获取不到怎么办?

A: iOS 14+ 需用户授权 ATT。请确认:

  • Info.plist 已配置 NSUserTrackingUsageDescription
  • 用户已授权 ATTrackingManager.AuthorizationStatus.authorized
  • 未授权时 SDK 仍可工作,但归因精度下降

Q: 广告为什么不显示?

A: 可能的原因:

  • 广告位 ID 不正确
  • 网络连接问题
  • 广告库存不足
  • 未在 onLoadSuccess 后调用 show()
  • 调用 show() 前未检查 isReady()

Q: iOS 错误码与 Android 不同吗?

A: 是的。iOS ErrorType 枚举为 1001-1008/9999,Android ErrorType 为 0/1000-2011。请分别参考各平台错误码文档。

技术支持

如果您在集成过程中遇到问题,可以通过以下方式获取支持:

相关链接