将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
相关产品推荐
相关产品推荐

