You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将AdMob授权逻辑迁移至Combine:解决GADMobileAds重复初始化问题

解决方案

以下是改造后的Combine实现,确保GADMobileAds.sharedInstance().start()仅执行一次,且每个Future只会被解析一次:

import UIKit
import GoogleMobileAds
import UserMessagingPlatform
import Combine

class ViewController: UIViewController {
    // 标记SDK是否已启动,避免重复执行
    private var isSDKStarted = false
    // 存储Combine订阅
    private var cancellables = Set<AnyCancellable>()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 启动Ads SDK并处理结果
        startAdsSDK()
            .sink(receiveCompletion: { completion in
                if case .failure(let error) = completion {
                    print("启动Ads SDK失败: \(error.localizedDescription)")
                }
            }, receiveValue: {
                print("Ads SDK启动成功")
                // TODO: 在此处请求广告
                // GADInterstitialAd.load(...)
            })
            .store(in: &cancellables)
    }
    
    /// 启动Ads SDK的Combine方法,返回Future表示启动结果
    /// - Returns: Future<Void, Error>,成功时返回Void,失败时返回Error
    private func startAdsSDK() -> Future<Void, Error> {
        Future { [weak self] promise in
            guard let self = self else {
                promise(.failure(selfDeallocatedError()))
                return
            }
            
            // 如果SDK已启动,直接返回成功
            if self.isSDKStarted {
                promise(.success(()))
                return
            }
            
            // 检查是否可立即请求广告(来自上一次会话的同意)
            if UMPConsentInformation.sharedInstance.canRequestAds {
                self.performSDKStart(promise: promise)
                return
            }
            
            // 执行同意流程
            self.requestConsentInfoUpdate()
                .flatMap { _ in self.presentConsentForm() }
                .sink(receiveCompletion: { [weak self] completion in
                    guard let self = self else {
                        promise(.failure(selfDeallocatedError()))
                        return
                    }
                    
                    switch completion {
                    case .failure(let error):
                        promise(.failure(error))
                    case .finished:
                        // 同意流程完成后检查是否可请求广告
                        guard UMPConsentInformation.sharedInstance.canRequestAds else {
                            promise(.failure(NSError(domain: "ConsentError", code: -1, userInfo: [NSLocalizedDescriptionKey: "同意流程完成后仍无法请求广告"])))
                            return
                        }
                        self.performSDKStart(promise: promise)
                    }
                }, receiveValue: {})
                .store(in: &self.cancellables)
        }
    }
    
    /// 封装UMP同意信息更新的Combine方法
    private func requestConsentInfoUpdate() -> Future<Void, Error> {
        Future { promise in
            UMPConsentInformation.sharedInstance.requestConsentInfoUpdate(with: nil) { error in
                if let error = error {
                    promise(.failure(error))
                } else {
                    promise(.success(()))
                }
            }
        }
    }
    
    /// 封装UMP同意表单加载与展示的Combine方法
    private func presentConsentForm() -> Future<Void, Error> {
        Future { [weak self] promise in
            guard let self = self else {
                promise(.failure(selfDeallocatedError()))
                return
            }
            
            UMPConsentForm.loadAndPresentIfRequired(from: self) { error in
                if let error = error {
                    promise(.failure(error))
                } else {
                    promise(.success(()))
                }
            }
        }
    }
    
    /// 实际执行SDK启动逻辑,确保仅执行一次
    private func performSDKStart(promise: @escaping (Result<Void, Error>) -> Void) {
        DispatchQueue.main.async { [weak self] in
            guard let self = self else {
                promise(.failure(selfDeallocatedError()))
                return
            }
            
            guard !self.isSDKStarted else {
                promise(.success(()))
                return
            }
            
            self.isSDKStarted = true
            GADMobileAds.sharedInstance().start()
            promise(.success(()))
        }
    }
    
    /// 辅助方法:创建Self已释放的错误
    private func selfDeallocatedError() -> Error {
        NSError(domain: "ViewControllerError", code: -1, userInfo: [NSLocalizedDescriptionKey: "ViewController已释放"])
    }
}

关键实现要点:

  • 单例式SDK启动:通过isSDKStarted标记确保GADMobileAds.start()仅执行一次,即使多个分支尝试触发。
  • Combine封装:将UMP的回调API封装为Future类型的Publisher,统一异步流程处理。
  • Future单次解析:每个Future实例只会被解析一次,符合Future的单发射特性。
  • 错误处理:完整覆盖同意流程中的错误场景,以及ViewController提前释放的边界情况。
  • 分支逻辑整合:将原代码中两个可能触发SDK启动的分支(立即检查、同意流程完成)整合到一个startAdsSDK方法中,确保逻辑一致性。

内容的提问来源于stack exchange,提问作者Gargo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 20:25:23