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. 构造初始化配置
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 中完成初始化:
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 字段说明
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
appId | String | 是 | - | 优聚智慧后台分配的应用 ID |
appKey | String | 是 | - | 优聚智慧后台分配的 RSA 公钥(非普通密钥) |
channel | String | 否 | "" | 渠道标识,用于数据统计与分账 |
subChannel | String | 否 | "" | 子渠道 |
isDebug | Bool | 否 | false | 调试模式,发布前请设为 false |
wxAppId | String | 否 | "" | 微信 AppId,用于 Deeplink 归因 |
privacyConfig | UjuPrivacyConfig? | 否 | nil | 隐私合规配置 |
personalization | UjuPersonalizedConfig? | 否 | nil | 个性化用户信息 |
presetStrategyFileName | String | 否 | "" | 离线策略文件名(placement_config.json) |
4. 个性化配置 UjuPersonalizedConfig
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
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
useLocation | Bool | true | 是否使用位置信息 |
useAppList | Bool | true | 是否使用已安装应用列表 |
usePhoneState | Bool | true | 是否使用设备状态 |
useWifiState | Bool | true | 是否使用 WiFi 状态 |
imei | String? | nil | 开发者自定义 IMEI |
idfa | String? | nil | 开发者自定义 IDFA |
idfv | String? | nil | 开发者自定义 IDFV |
macAddress | String? | nil | 开发者自定义 MAC |
广告加载通用流程
所有广告类型遵循统一流程:
- 构造
UjuAdConfig(placementId:...) - 调用
UjuAdSdk.shared.createXxxAd(viewController:config:)获取UjuAdObjectProtocol setAdObjectListener(_:)设置监听器load()加载广告onLoadSuccess回调后,调用show()(激励/插屏)或show(in: container)(开屏/横幅/原生)- 不再使用时调用
destroy()
UjuAdConfig 字段说明
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
placementId | String | 是 | - | 广告位 ID(precondition 校验非空) |
scenarioKey | String | 否 | "" | 广告展示场景标识,用于统计 |
adViewSize | AdViewSize | 否 | AdViewSize() | 广告尺寸,仅对模板信息流(FeedType.express)生效 |
adFormat | AdFormat | 否 | .native | 广告格式(工厂方法会自动覆盖) |
userId | String? | 否 | nil | 用户 ID,激励视频常用 |
customData | [String: String] | 否 | [:] | 自定义数据,透传给广告服务端 |
监听器签名说明
iOS SDK 监听器与 Android 对齐,需特别注意加载失败与展示失败的参数数量差异:
| 回调 | 参数数量 | 所属协议 | 触发阶段 |
|---|---|---|---|
onLoadSuccess(_ placementId: String) | 1 | BaseAdObjectListener | 加载成功 |
onLoadError(_ error: UjuException, placementId: String) | 2 | BaseAdObjectListener | 加载失败 |
onAdError(_ error: UjuException) | 1 | 各子监听器 | 展示阶段失败 |
所有协议均有默认空实现(
public extension),调用方可只实现关心的回调。
广告类型集成
1. 开屏广告
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. 激励视频广告
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. 插屏广告
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. 横幅广告
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 不同)。
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 公共方法
| 方法 | 返回值 | 说明 |
|---|---|---|
shared | UjuAdSdk | 单例入口 |
init(config: UjuAdInitConfig) | Void | 第一阶段:配置初始化(不采集隐私) |
start(listener: BaseInitListener?) | Void | 第二阶段:正式启动 SDK |
isSdkInitialized() | Bool | 是否已调用 init |
getInitializeState() | UjuAdInitStatus | 获取初始化状态枚举 |
getVersion() | String | 获取 SDK 版本号 |
getAppId() | String | 获取应用 ID |
getOAID() | String | iOS 对应 IDFA,未获取返回空串 |
getGoogleAdId() | String | 获取 GoogleAdId |
updateChannel(_ channel: String, subChannel: String) | Void | 更新渠道 |
registerAdapterFactory(_ factory: BaseAdapterFactory) | Bool | 注册单个适配器工厂 |
registerAdapterFactories(_ factories: [BaseAdapterFactory]) | Void | 批量注册 |
getRegisteredPlatforms() | [PlatformInstanceKey] | 查询已注册平台 |
destroy() | Void | 释放 SDK 资源 |
枚举参考
AdFormat(广告格式)
| 值 | 枚举 | 说明 |
|---|---|---|
| 1 | native | 原生信息流 |
| 2 | reward | 激励视频 |
| 3 | banner | 横幅 |
| 4 | interstitial | 插屏 |
| 5 | splash | 开屏 |
| 6 | videoStream | 视频流 |
FeedType(信息流渲染类型)
| 值 | 枚举 | 说明 |
|---|---|---|
| 1 | express | 模板渲染 |
| 2 | unified | 自渲染 |
AdMediaType(自渲染媒体类型)
| 值 | 枚举 | 说明 |
|---|---|---|
| 1 | singleImage | 单图 |
| 2 | multiImage | 多图 |
| 3 | video | 视频 |
AdBidType(竞价类型)
| 值 | 枚举 | 说明 |
|---|---|---|
| 1 | normal | 瀑布流 |
| 2 | adx | ADX 竞价 |
| 3 | c2s | 客户端竞价 |
| 4 | s2s | 服务端竞价 |
AdPlatformType(广告平台)
| 值 | 枚举 | 说明 |
|---|---|---|
| 1 | csj | 穿山甲 |
| 2 | ylh | 优量汇 |
| 3 | bd | 百度 |
| 4 | ks | 快手 |
| 6 | oppo | OPPO |
| 7 | vivo | VIVO |
| 100 | adx | 优聚内部 ADX |
UjuAdInitStatus(初始化状态)
| 值 | 枚举 | 说明 |
|---|---|---|
| 0 | idle | 未初始化 |
| 1 | initializing | 初始化中 |
| 2 | initialized | 已初始化 |
| 3 | initializationFailed | 初始化失败 |
错误码(iOS ErrorType)
iOS SDK 错误码定义在 ErrorType 枚举中,与 Android 错误码不同,请勿混淆:
| 错误码 | 枚举 | 含义 |
|---|---|---|
| 1001 | initError | 初始化错误 |
| 1002 | networkError | 网络错误 |
| 1003 | configError | 配置错误 |
| 1004 | loadError | 加载错误 |
| 1005 | biddingError | 竞价错误 |
| 1006 | notFindAdapter | 未找到适配器 |
| 1007 | adShowError | 展示错误 |
| 1008 | adClickError | 点击错误 |
| 9999 | unknown | 未知错误 |
错误对象为 UjuException(public struct: Error),含 type: ErrorType、code: Int、message: String。
NativeAdData 字段
UjuAdObjectProtocol.getAdData() 返回 NativeAdData,用于自渲染填充数据:
| 字段 | 类型 | 说明 |
|---|---|---|
title | String? | 广告标题 |
desc | String? | 广告描述 |
source | String? | 广告来源 |
iconUrl | String? | 广告图标 URL |
mediaType | AdMediaType | 媒体类型(默认 .singleImage) |
imageUrl | String? | 单图 URL |
imageUrlList | [String]? | 多图 URL 列表 |
callToAction | String? | 行动号召文案 |
videoUrl | String? | 视频 URL |
clickUrl | String? | 点击 URL |
NativeAdViewBinder 字段
NativeAdViewBinder 用于自渲染绑定视图,iOS 通过 view.tag 标识组件(与 Android 的 resourceId 不同):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
titleTag | Int | 是 | 标题视图 tag |
descTag | Int | 是 | 描述视图 tag |
iconTag | Int | 是 | 图标视图 tag |
imageTag | Int | 是 | 主图视图 tag |
ctaTag | Int | 是 | 行动号召按钮 tag |
sourceTag | Int? | 否 | 来源视图 tag |
mediaViewTag | Int? | 否 | 媒体视图 tag(视频广告) |
logoTag | Int? | 否 | 广告标识 tag |
clickViewTags | [Int]? | 否 | 额外可点击视图 tag 列表 |
dislikeTag | Int? | 否 | 不感兴趣按钮 tag |
UjuAdInfo 字段
UjuAdObjectProtocol.getAdInfo() 返回 UjuAdInfo,包含广告元数据:
| 字段 | 类型 | 说明 |
|---|---|---|
ecpm | Double | 广告出价(eCPM,应用 revenueRate 后) |
placementId | String | 广告位 ID |
soltId | String | 广告代码位 ID(注:源码拼写为 soltId) |
platformId | Int | 广告平台 ID(参见 AdPlatformType) |
adFormat | AdFormat | 广告格式 |
originalDoubleData | Double | 原始 eCPM(未转换) |
生命周期与销毁
- 每个
UjuAdObjectProtocol不再使用时必须调用destroy()释放资源 - 应用退出时调用
UjuAdSdk.shared.destroy()释放 SDK 资源 destroy()后不要再调用任何方法或触发展示上报
常见问题
Q: SDK 初始化失败怎么办?
A: 检查以下几点:
appId与appKey(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。请分别参考各平台错误码文档。
技术支持
如果您在集成过程中遇到问题,可以通过以下方式获取支持:
- 邮件:marco@ujuad.com
- 官网:https://www.ujuad.com
相关链接
- iOS 集成准备
- SDK 下载
- Android SDK 初始化(iOS API 与 Android 高度对齐,可对照参考)
- Android 错误码(注意:iOS 错误码与 Android 不同)
