第三方广告聚合框架设计

简介: 该框架的设计初衷是集中管理第三方的广告服务,包括服务的环境配置,初始化,以及广告的加载与展示

前言

该框架的设计初衷是集中管理第三方的广告服务,包括服务的环境配置,初始化,以及广告的加载与展示。

接入过程中,我们会试用相同的策略,管理不同的服务,所以需要抽象一套对服务的统一抽象协议。

服务协议

将集成第三方服务大致分为3个部分:环境配置,服务SDK参数配置,广告预载与展示。

平台服务协议

public protocol GHADPlatfromServiceDelegate: NSObjectProtocol {
   
    func didSetup(service: GHADPlatfromService, params: [String : Any])
}

public protocol GHADPlatfromService {
   
    var delegate: GHADPlatfromServiceDelegate? {
    get set }
    var setupParams: [String : Any] {
    get set }
    func setup() -> Bool
    func configUserIdentity(params: [String : Any]) -> Bool
    func checkMainBundleInfoValid() -> Bool
}

GHADPlatfromService协议用于管理第三方服务的环境配置,以及SDK服务的初始化。

  • GHADPlatfromServiceDelegate:平台服务的生命周期回调

  • checkMainBundleInfoValid:部分第三方广告服务通过Info.plist配置注册信息,这里提供了用于做配置校验。

  • setupParamssetup:用于对服务SDK的参数配置,会在setup中进行调用。当出现多个服务商SDK时,业务方可能会统一配置服务参数,但对服务实行懒加载初始化,所以分离了参数配置和初始化的API。

  • configUserIdentity:配置用户信息

广告服务协议

public protocol GHAdDelegate: NSObjectProtocol {
   
    func adDidLoad(_ ad: GHAd)
    func adDidFailToLoadAd(_ ad: GHAd, error: Error?)

    func adDidDisplay(_ ad: GHAd)
    func adDidHide(_ ad: GHAd)
    func adDidClick(_ ad: GHAd)
    func adDidFail(_ ad: GHAd, error: Error?)

    func adDidStartRewardedVideo(_ ad: GHAd)
    func adDidCompleteRewardedVideo(_ ad: GHAd)
    func adDidRewardUser(_ ad: GHAd, name: String, amount: Double)
}

public protocol GHAd {
   
    var delegate: GHAdDelegate? {
    get set }
    func load(params: [String: Any], completion: ((Bool, NSError?) -> Void)?)
    func show(params: [String: Any], rewardUser: ((_ ad: GHAd, _ name: String, _ amount: Double) -> Void)?, completion: ((Bool, NSError?) -> Void)?)
}

GHADPlatfromService协议用于桥接服务商SDK提供的API,以及广告声明周期的回调。

  • GHAdDelegate:统一服务商SDK中关于广告展示的生命周期回调
  • load:加载广告
  • show:显示广告

这个协议的作用是抹平不同服务商SDK之间的差异,统一广告管理逻辑。

激励类型视频广告

GHAppLovinRewardedAd 是对AppLovin服务商提供的激励类型视频的一个具体封装。

它遵循了GHAd协议,服务商提供的回调MARewardedAdDelegate桥接到GHAdDelegate

实现了失败自动重新加载,下一则广告预载,避免重复加载等策略。

import Foundation
import AppLovinSDK
import GHToolKit
import GHLogger

public class GHAppLovinRewardedAd: NSObject, GHAppLovinAd {
   

    public let platformServiceType: GHADServiceType = .appLovin
    public weak var delegate: GHAdDelegate?
    public weak var revenueDelegate: MAAdRevenueDelegate?
    public let adType: GHAppLovinAdType = .reward
    public var isLoading: Bool = false
    public var isReady: Bool {
    return ad.isReady }

    public var adUnitId: String
    public var retryAttempt = 0
    public var maxRetryCount = 0
    public var autoRetryLoadWhenFailed: Bool = false
    public var autoLoadNextAd: Bool = true

    var loadCompletion: ((Bool, NSError?) -> Void)?
    var showCompletion: ((Bool, NSError?) -> Void)?
    var rewardUserCompletion: ((_ ad: GHAd, _ name: String, _ amount: Double) -> Void)?

    lazy var ad: MARewardedAd = {
   
        guard let service = GHADManager.shared.platformService(type: .appLovin) as? GHAppLovinService,
           let sdk = service.sdk else
        {
   
            return MARewardedAd.shared(withAdUnitIdentifier: self.adUnitId)
        }

        let ad = MARewardedAd.shared(withAdUnitIdentifier: self.adUnitId, sdk: sdk)
        ad.delegate = self
        ad.revenueDelegate = self

        return ad
    }()

    @objc public init(adUnitId: String, delegate: GHAdDelegate? = nil) {
   
        self.delegate = delegate
        self.adUnitId = adUnitId
    }
}

public extension GHAppLovinRewardedAd {
   

    @objc func load(params: [String: Any] = [String : Any](), completion: ((Bool, NSError?) -> Void)? = nil) {
   
        let forceLoad = params[GHAppLovinServiceParamKey.forceLoad.rawValue] as? Bool
        guard let forceLoad = forceLoad, forceLoad else {
   
            guard !ad.isReady else {
   
                completion?(false, GHError.error(code: -1, userInfo: ["Message": "ad is already load"]))
                return
            }
            ad.load()
            isLoading = true
            if let exist = loadCompletion {
    exist(false, nil) }
            loadCompletion = completion
            return
        }
        ad.load()
        isLoading = true
        if let exist = loadCompletion {
    exist(false, nil) }
        loadCompletion = completion
    }

    @objc func show(params: [String: Any] = [String : Any](), rewardUser: ((_ ad: GHAd, _ name: String, _ amount: Double) -> Void)? = nil, completion: ((Bool, NSError?) -> Void)? = nil) {
   
        rewardUserCompletion = rewardUser
        if let exist = showCompletion {
    exist(false, nil) }
        showCompletion = completion
        if ad.isReady {
   
            ad.show()
            return
        }
        load {
    [weak self] success, error in
            guard let self = self else {
    return }
            if success {
   
                self.ad.show()
            } else {
   
                self.showCompletion?(false, error)
                self.showCompletion = nil
                self.rewardUserCompletion = nil
            }
        }
    }
}

// MARK: - MARewardedAdDelegate
extension GHAppLovinRewardedAd : MARewardedAdDelegate {
   

    public func didLoad(_ ad: MAAd) {
   
        isLoading = false
        logDebug("didLoad", tag: .ad)
        GHADLogEventManager.shared.adDidLoad(self)
        delegate?.adDidLoad?(self)
        loadCompletion?(true, nil)
        loadCompletion = nil
        retryAttempt = 0
    }

    public func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
   
        isLoading = false
        let error = GHError.error(code: error.code.rawValue, userInfo: ["message": error.message, "networkMessage": error.mediatedNetworkErrorMessage])
        logWarn("didFailToLoadAd: \(String(describing: error))", tag: .ad)
        GHADLogEventManager.shared.adDidFailToLoadAd(self, error: error)
        delegate?.adDidFailToLoadAd?(self, error: error)
        loadCompletion?(false, error)
        loadCompletion = nil
        if autoRetryLoadWhenFailed {
   
            if maxRetryCount > 0, retryAttempt < maxRetryCount {
   
                retryAttempt += 1
                let delaySec = pow(2.0, min(6.0, Double(retryAttempt)))
                DispatchQueue.main.asyncAfter(deadline: .now() + delaySec) {
   
                    self.ad.load()
                }
            } else {
   
                retryAttempt = 0
            }
        }
    }

    public func didStartRewardedVideo(for ad: MAAd) {
   
        GHADLogEventManager.shared.adDidStartRewardedVideo(self)
        delegate?.adDidStartRewardedVideo?(self)
    }

    public func didCompleteRewardedVideo(for ad: MAAd) {
   
        GHADLogEventManager.shared.adDidCompleteRewardedVideo(self)
        delegate?.adDidCompleteRewardedVideo?(self)
    }

    public func didRewardUser(for ad: MAAd, with reward: MAReward) {
   
        logDebug("didRewardUser: \(String(describing: reward))", tag: .ad)
        let name = reward.label
        let amount = Double(reward.amount)
        GHADLogEventManager.shared.adDidRewardUser(self, name: name, amount: amount)
        delegate?.adDidRewardUser?(self, name: name, amount: amount)
        self.rewardUserCompletion?(self, name, amount)
        self.rewardUserCompletion = nil
    }

    public func didDisplay(_ ad: MAAd) {
   
        logDebug("didDisplay", tag: .ad)
        showCompletion?(true, nil)
        showCompletion = nil
        GHADLogEventManager.shared.adDidDisplay(self)
        delegate?.adDidDisplay?(self)
    }

    public func didHide(_ ad: MAAd) {
   
        logDebug("didHide", tag: .ad)
        GHADLogEventManager.shared.adDidHide(self)
        delegate?.adDidHide?(self)
        // Rewarded ad is hidden. Pre-load the next ad
        if autoLoadNextAd {
    self.ad.load() }
        rewardUserCompletion = nil
    }

    public func didClick(_ ad: MAAd) {
   
        logDebug("didClick", tag: .ad)
        GHADLogEventManager.shared.adDidClick(self)
        delegate?.adDidClick?(self)
    }

    public func didFail(toDisplay ad: MAAd, withError error: MAError) {
   
        let error = GHError.error(code: error.code.rawValue, userInfo: ["message": error.message, "networkMessage": error.mediatedNetworkErrorMessage])
        logWarn("didFail: \(String(describing: error))", tag: .ad)
        showCompletion?(false, error)
        showCompletion = nil
        rewardUserCompletion = nil
        GHADLogEventManager.shared.adDidFailToDisplay(self, error: error)
        delegate?.adDidFailToDisplay?(self, error: error)
        // Rewarded ad failed to display. We recommend loading the next ad
        if autoLoadNextAd {
    self.ad.load() }
    }
}

// MARK: - MAAdRevenueDelegate

extension GHAppLovinRewardedAd: MAAdRevenueDelegate {
   

    public func didPayRevenue(for ad: MAAd) {
   
        delegate?.adDidPayRevenue?(self, maAd: ad)
    }

}

广告模块注册与使用

广告模块的使用分为3个步骤

  1. 注册平台服务,配置服务商Key、用户信息,并注册到GHADManager进行统一维护。
  2. 生成一个广告实例(通常广告使用单例模式管理),配置广告的加载策略。
  3. 使用广告的showAPI进行展示
/// 配置广告
class AdModule: NSObject, SpaceportModuleProtocol {
   
    var loaded = false
    static var rewardedAd: GHAppLovinRewardedAd?
    static func modulePriority() -> Int {
    return 2750 }

    func loadModule() {
   
        let servce = GHAppLovinService()
        let key = "xxx"
        servce.setupParams[GHAppLovinServiceParamKey.sdkKey.rawValue] = key

        GHADManager.shared.register(type: .appLovin, service: servce)
        GHADManager.shared.setupServices()

        let rewardedAd = GHAppLovinRewardedAd(adUnitId: "c3ae87d19c167b0b")
        rewardedAd.autoRetryLoadWhenFailed = true
        rewardedAd.maxRetryCount = 10
        rewardedAd.delegate = self
        Self.rewardedAd = rewardedAd
        preloadAd()
    }

    func applicationDidBecomeActive(notification: Notification) {
   
        preloadAd()
    }

    func preloadAd() {
   
        // 非VIP,预载广告
        if !PurchaseManager.isVIP() {
   
            Self.rewardedAd?.load()
        }
    }

    func unloadModule() {
    }
}

// MARK: - GHAdDelegate
extension AdModule: GHAdDelegate {
   
    func adDidLoad(_ ad: GHAD.GHAd) {
    }
    func adDidFailToLoadAd(_ ad: GHAD.GHAd, error: Error?) {
    }
    func adDidDisplay(_ ad: GHAD.GHAd) {
    }
    func adDidHide(_ ad: GHAD.GHAd) {
    }
    func adDidClick(_ ad: GHAD.GHAd) {
    }
    func adDidFail(_ ad: GHAD.GHAd, error: Error?) {
    }
    func adDidStartRewardedVideo(_ ad: GHAD.GHAd) {
    }
    func adDidCompleteRewardedVideo(_ ad: GHAD.GHAd) {
    }
    func adDidRewardUser(_ ad: GHAd, name: String, amount: Double) {
    }
}
// 展示广告
func showAd() {
   
  guard let ad = AdModule.rewardedAd else {
    return }
  ad.show {
    ad, name, amount in
      // 激励成功回调
            PEHUD.showRewardedADSuccess {
   }
    } completion: {
    success, error in
      // 展示完成回调
      if !success {
    PEHUD.showRewardedADFailed() }
  }  
}

总结

该聚合框架的重点会放在服务SDK的注册与管理,抹平不同SDK的差异,减少业务方接入成本与使用成本。

同时采用服务协议化注册,在实现统一API的基础上,满足业务方扩展定制化API的需求。

另外由于这是一个聚合框架,会包含所有服务商的SDK,当集成的服务商数量太多时,会明显影响包体大小。

后续使用过程中,应当按需组合SDK,避免这个问题。

目录
相关文章
|
1月前
|
供应链 搜索推荐 数据挖掘
1688搜索词推荐API接口:开发应用与收益全解析
在电商数据驱动时代,1688搜索词推荐API接口为开发者、供应商及电商从业者提供强大工具,优化业务流程,提升竞争力。该接口基于1688平台的海量数据,提供精准搜索词推荐,助力电商平台优化搜索体验,提高供应商商品曝光度与销售转化率,同时为企业提供市场分析与商业洞察,促进精准决策与成本降低。通过集成此API,各方可实现流量增长、销售额提升及运营优化,推动电商行业的创新发展。
34 0
|
4月前
|
图形学 开发者 搜索推荐
Unity Asset Store资源大解密:自制与现成素材的优劣对比分析,教你如何巧用海量资产加速游戏开发进度
【8月更文挑战第31天】游戏开发充满挑战,尤其对独立开发者或小团队而言。Unity Asset Store 提供了丰富的资源库,涵盖美术、模板、音频和脚本等,能显著加快开发进度。自制资源虽具个性化,但耗时长且需专业技能;而 Asset Store 的资源经官方审核,质量可靠,可大幅缩短开发周期,使开发者更专注于核心玩法。然而,使用第三方资源需注意版权问题,且可能需调整以适应特定需求。总体而言,合理利用 Asset Store 能显著提升开发效率和项目质量。
111 0
|
8月前
|
小程序 开发者 索引
微信小游戏如何接入多种类型的广告?
微信小游戏如何接入多种类型的广告?
320 1
|
监控 BI 定位技术
直播程序源码开发建设:洞察全局,数据统计与分析功能
数据统计与分析功能不管是对直播程序源码平台的主播或运营者都会有极大的帮助,是了解观众需求、优化用户体验成为直播平台发展的关键功能,这也是开发搭建直播程序源码平台的必备功能之一。
直播程序源码开发建设:洞察全局,数据统计与分析功能
量化合约系统开发正式版/规则详细/方案逻辑/功能设计/项目案例/源码出售
The development process of a quantitative contract system refers to the design and development of a trading system for executing automated trading strategies.
|
搜索推荐 前端开发 Java
聚合CPS返利平台开发功能部署设计
聚合CPS返利平台开发功能部署设计
|
存储 算法 安全
Jogger跑鞋链游开发详情丨Jogger链游跑鞋系统开发方案详细/项目逻辑/功能分析/案例设计/源码平台
  区块链就是把加密数据(区块)按照时间顺序进行叠加(链)生成的永久、不可逆向修改的记录。某种意义上说,区块链技术是互联网时代一种新的“信息传递”技术,
|
机器学习/深度学习 算法 搜索推荐
怎样设计一个广告系统
怎样设计一个广告系统
404 0
怎样设计一个广告系统
|
搜索推荐
《淘系千人千面分发体系全拆解》电子版下载
《淘系千人千面分发体系全拆解》9-1
59 0
《淘系千人千面分发体系全拆解》电子版下载
|
开发者
营销引擎 - 广告主如何使用借助营销引擎快速搭建的 DSP|学习笔记
快速学习营销引擎 - 广告主如何使用借助营销引擎快速搭建的 DSP
营销引擎 - 广告主如何使用借助营销引擎快速搭建的 DSP|学习笔记