Swift中Admob插页式广告回调触发不稳定 脱离Xcode运行时完全失效
核心故障原因分析
1. 广告管理类的生命周期持有失效
- 你将
Interstitial(引用类型)直接作为SwiftUI值类型View的成员变量,且没有通过@StateObject做长期强引用。SwiftUI的View是值语义,每次视图重绘、视图销毁时,这个引用类型成员的强引用会随View实例一起释放,导致Interstitial实例在广告加载完成、回调触发前就被系统回收——对象回收后,原本作为GADFullScreenContentDelegate的回调自然无法执行,通知也无法发送。 - 这种生命周期的不稳定性,直接导致了回调“时灵时不灵”的现象。
2. 脱离Xcode环境时,线程违规导致回调静默失效
- 谷歌AdMob的所有广告相关回调,默认是在后台线程触发的。你直接在回调方法里post
UserClosedAd通知,没有切换到主线程执行;而SwiftUI的.onReceive修饰符要求必须在主线程处理视图状态更新。 - 当连接Xcode调试时,系统会对后台线程触发的UI操作做拦截提示,但不会直接中断流程;当脱离Xcode环境时,这种跨线程的状态更新会直接触发运行时崩溃,且崩溃发生在后台线程、没有用户可见的异常提示,就会表现为“回调完全不生效”。
3. SwiftUI App生命周期下的根视图获取逻辑失效
- 你使用的
UIApplication.shared.windows.first?.rootViewController是基于UIKit生命周期、SceneDelegate管理window的旧实现,在纯SwiftUI App生命周期下,这个API在iOS15+已经被标记废弃,多数场景下返回值为nil。 - 当获取到的根视图控制器为nil时,你代码中的
root!强解包会直接触发崩溃;即便没有解包崩溃,广告的present操作也因为没有有效的锚点视图控制器完全无法执行,自然不会触发后续的广告展示、关闭回调。
4. 通知订阅的生命周期缺失
- 你在View结构体中直接创建
NotificationCenter.default.publisher(...)实例,没有将对应的Combine订阅通过AnyCancellable做长期持有。由于SwiftUI View是值类型,每次视图重绘时这个publisher实例会被重新创建,之前的订阅会立即被释放,导致通知post出去后根本没有有效的订阅者接收,.onReceive的回调逻辑自然无法触发。
5. 冗余协议继承带来的额外风险
- 你让
Interstitial类遵循UIApplicationDelegate协议,但这个类本身并不是App的代理实现(App代理是SwiftUI App入口的类型),多余的协议遵循会导致类的方法调用链出现异常,进一步降低回调的稳定性。
针对性修正方向
- 将
Interstitial改造为遵循ObservableObject的类,在View中通过@StateObject修饰符持有实例,确保SwiftUI对这个对象的长期强引用,避免对象被提前回收。 - 所有广告回调、状态变更逻辑,统一通过
DispatchQueue.main.async切换到主线程执行,禁止在后台线程做视图操作、状态更新。 - 替换根视图控制器的获取逻辑:通过遍历当前活跃的
UIWindowScene,找到对应场景的keyWindow获取其根视图控制器,去掉强解包,对nil场景做兜底处理。 - 去掉跨层的NotificationCenter通信,改用
@Published属性在Interstitial类中对外暴露广告的加载、关闭、展示状态,让View直接订阅这个状态值,从根源上避免通知发/收不匹配的问题。 - 移除
Interstitial类上冗余的UIApplicationDelegate协议遵循,减少不必要的协议方法依赖。
内容的提问来源于stack exchange,提问作者Onnwen
相关产品推荐
相关产品推荐

