前言
该框架的设计初衷是集中管理第三方的广告服务,包括服务的环境配置,初始化,以及广告的加载与展示。
接入过程中,我们会试用相同的策略,管理不同的服务,所以需要抽象一套对服务的统一抽象协议。
服务协议
将集成第三方服务大致分为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配置注册信息,这里提供了用于做配置校验。setupParams
和setup
:用于对服务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个步骤
- 注册平台服务,配置服务商Key、用户信息,并注册到
GHADManager
进行统一维护。 - 生成一个广告实例(通常广告使用单例模式管理),配置广告的加载策略。
- 使用广告的
show
API进行展示
/// 配置广告
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,避免这个问题。