Unity MonoBehaviour单例OnDestroy执行顺序差异问题咨询
生命周期执行顺序差异的根本原因
Unity对DontDestroyOnLoad标记对象的销毁顺序,由对象的初始归属直接决定:
- 场景内预先挂载的Counter,是在场景加载时先被创建为当前场景对象,Awake中调用
DontDestroyOnLoad才移入全局持久场景。引擎退出或卸载场景时,会优先处理当前场景原生的所有对象(包括场景中挂载的Observable),再处理从该场景移入持久场景的对象,因此Observable会比Counter更早触发OnDisable、OnDestroy回调。 - 懒加载创建的Counter,是代码动态生成GameObject后直接调用
DontDestroyOnLoad,从创建初始就完全归属全局持久场景。引擎退出时会先清空全局持久场景的所有对象,再处理当前激活场景内的普通对象,因此Counter会比场景原生的Observable更早执行销毁回调。
你观察到的Some objects were not cleaned up when closing the scene报错,本质是销毁顺序反转后,Observable在OnDestroy中访问单例实例时,原Counter已经被销毁、静态_instance引用为空,触发懒创建逻辑在退出阶段生成了无法被正常清理的新对象导致的。
可落地的修复方案
- 给单例基类增加退出状态拦截,从根源禁止退出阶段创建新实例
在基类中增加应用退出标记,在退出流程中将标记置为true,instance访问逻辑优先判断该标记,退出阶段直接返回null,不执行创建逻辑;同时注意只有非退出阶段的对象销毁,才需要清空静态实例引用,避免误触发创建。核心修改代码如下:public class SingletonMonoBehaviour<T> : MonoBehaviour where T: SingletonMonoBehaviour<T> { private static T _instance; private static bool _isApplicationQuitting = false; public static T instance { get { if (_isApplicationQuitting) return null; if (_instance == null) { var go = new GameObject(typeof(T).Name); _instance = go.AddComponent<T>(); DontDestroyOnLoad(_instance); } return _instance; } } protected virtual void Awake() { if (_instance != null && _instance != this) { Destroy(this.gameObject); } else { _instance = (T)this; DontDestroyOnLoad(this); } } protected virtual void OnApplicationQuit() { _isApplicationQuitting = true; } private void OnDisable() { Debug.Log("OnDisable " + this.name); } private void OnDestroy() { if (!_isApplicationQuitting) { _instance = null; } Debug.Log("OnDestroy " + this.name); } } - 统一单例创建路径
如果需要固定全局对象的销毁顺序,可以在项目的启动初始化场景(首个加载的场景)中主动访问所有全局单例的instance属性,或直接在启动场景预先挂载所有单例组件,保证所有单例的创建来源一致,避免因归属不同导致的销毁顺序差异。 - 优化事件绑定逻辑
全局单例的事件注销尽量放在OnDisable阶段执行,访问单例实例前增加空判断,不要依赖销毁顺序保证逻辑正确性。
内容的提问来源于stack exchange,提问作者Евгений Латынин
相关产品推荐
相关产品推荐

