第三方广告聚合框架设计

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

前言

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

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

服务协议

将集成第三方服务大致分为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,避免这个问题。

目录
相关文章
|
23天前
|
供应链 搜索推荐 数据挖掘
1688搜索词推荐API接口:开发应用与收益全解析
在电商数据驱动时代,1688搜索词推荐API接口为开发者、供应商及电商从业者提供强大工具,优化业务流程,提升竞争力。该接口基于1688平台的海量数据,提供精准搜索词推荐,助力电商平台优化搜索体验,提高供应商商品曝光度与销售转化率,同时为企业提供市场分析与商业洞察,促进精准决策与成本降低。通过集成此API,各方可实现流量增长、销售额提升及运营优化,推动电商行业的创新发展。
30 0
|
8月前
|
搜索推荐 数据管理 数据挖掘
解码2024年项目管理系统:排行榜背后的功能与特色解析
2024年十大项目管理工具:Zoho Projects以其专业成熟度领先,适合跨部门协作和进度跟踪;Nifty适合初创公司,界面直观,响应快速;Quickbase面向处理大量信息的团队,提供定制化解决方案;WorkOtter专为中大型企业资源管理和汇报设计;Asana适合大型协作团队,任务管理和沟通高效;Monday.com高度可定制,适合复杂项目管理;Smartsheet结合电子表格功能,适合流程多变的团队;Adobe Workfront针对复杂项目和自动化需求;ClickUp是一站式工作平台,功能多样;Trello则以简洁看板适合小团队和个人。考虑团队规模、项目复杂度和个性化需求来选工具
83 1
|
8月前
|
小程序 开发者
【社区每周】小程序商品能力两项接口变动(11月第三期)
【社区每周】小程序商品能力两项接口变动(11月第三期)
80 10
|
监控 BI 定位技术
直播程序源码开发建设:洞察全局,数据统计与分析功能
数据统计与分析功能不管是对直播程序源码平台的主播或运营者都会有极大的帮助,是了解观众需求、优化用户体验成为直播平台发展的关键功能,这也是开发搭建直播程序源码平台的必备功能之一。
直播程序源码开发建设:洞察全局,数据统计与分析功能
|
存储 安全
DAPP/3M互助拆分公排双轨系统开发详细逻辑/案例分析/方案项目/技术分析/源码平台
 DApp是指基于区块练技术的去中心化应用程序,它的特点是去中心化、透明、安全、不可篡改等特点。
|
新零售 搜索推荐 数据挖掘
短剧CPS系统开发规则详细/案例项目/成熟技术/源码方案
开发新零售是指利用科技和创新的方法,开发和构建与新零售概念相符的电子商务系统、应用和平台等,以满足消费者的个性化需求并提供全渠道的零售体验。
|
搜索推荐 前端开发 Java
聚合CPS返利平台开发功能部署设计
聚合CPS返利平台开发功能部署设计
|
算法 机器人 区块链
数字货币量化机器人系统开发(项目案例)/功能说明/逻辑方案/源码平台
  简单地说,量化交易机器人就是能够自动执行交易策略的交易软件。它借助于计算机技术和数学模型,对市场行情进行分析预测,并根据程序设定的规则和条件自动执行交易策略,完成交易操作。Compared with traditional manual trading,quantitative trading robots have faster trading speed,lower transaction costs,and higher trading efficiency.
|
区块链
OPensea /nft交易平台分红项目系统开发项目方案/功能说明/方案逻辑/源码详情
简单来说,DAPP和普通的App原理一样,除了他们是完全去中心化的,由类似以太坊网络本身自己的节点来运作的DAPP,不依赖于任何中心化的服务器,DAPP是去中心化的,可以完全自动地运行。
|
NoSQL 区块链 Redis
区块链聚合交易所平台开发源码实例分析
区块链聚合交易所平台开发源码实例分析

热门文章

最新文章