Unity中Mono脚本单例模式

本文分享Unity中Mono脚本单例模式

一般在项目中, 我们会使用一些全局的Mono对象, 但是只需要一份, 这时候单例模式是很适合的.

今天给大家介绍下Mono类型的单例怎么写.

非线程安全

在Unity开发中, 大部分情况下我们不需要考虑线程安全的问题, 比较简单的一种实现如下:

    public class MonoSingletonTest : MonoBehaviour
    {
        private static MonoSingletonTest s_Instance;

        public static MonoSingletonTest instance
        {
            get
            {
                if (s_Instance != null) return s_Instance;

                s_Instance = FindObjectOfType();
                if (s_Instance == null)
                {
                    var go = new GameObject("MonoSingletonTest");
                    s_Instance = go.AddComponent();
                }
                
                s_Instance.Init();

                return s_Instance;
            }
        }

        private void Init()
        {
            DontDestroyOnLoad(gameObject);
        }
    }

以上代码使用"懒汉"模式, 即在第一次使用的时候进行单例化.

代码兼容场景初始化时挂载或者没有挂载MonoSingletonTest脚本的情况.


线程安全

在大部分情况下, 上面的方式已经足够使用了, 但是也不排除我们某些类需要考虑线程安全的问题, 以下是一种比较经典的写法:

public static MonoSingletonTest instanceSafe
{
    get
    {
        if (s_Instance == null)
        {
            lock (typeof(MonoSingletonTest))
            {
                if (s_Instance == null)
                {
                    s_Instance = FindObjectOfType();
                    if (s_Instance == null)
                    {
                        var go = new GameObject("MonoSingletonTest");
                        s_Instance = go.AddComponent();
                    }

                    s_Instance.Init();
                }
            }
        }

        return s_Instance;
    }
}

上面的代码使用了一种所谓"双重效验锁(DCL)"的机制来保证线程安全.

在非Mono脚本的单例中我们一般使用"Lazy "类(.net 4.0引入, 内部默认使用DCL)来保证线程安全, 我们将在另外的文章中分享.

双重效验锁(Double-Check Locking, DCL)

什么是DCL呢? 简单讲就是使用两个判空条件来检查是否是单个实例.

第一个判空用于介绍对锁资源的争夺, 当实例存在时, 不再需要通过锁来判断实例的存在, 这样可以大大降低资源的浪费.

当多个线程通过了第一个判空后, 由于线程锁的原因, 只有一个线程能通过第7行, 其它线程会被阻挡在外.

当某个线程完成了实例的初始化之后, 其它某个被卡在第7行的线程获得了资源, 通过了第7行, 但是在第9行, 即第二个判空时被阻挡. 这也是整个效验最关键的地方.

泛型写法

public class MonoSingleton : MonoBehaviour where T : MonoBehaviour
{
    private static T s_Instance;

    public static T instance
    {
        get
        {
            if (s_Instance == null)
            {
                lock (typeof(T))
                {
                    if (s_Instance == null)
                    {
                        s_Instance = FindObjectOfType();
                        if (s_Instance == null)
                        {
                            var go = new GameObject(typeof(T).Name);
                            s_Instance = go.AddComponent();
                        }
                    }
                }
            }

            return s_Instance;
        }
    }

    public static T instance2
    {
        get
        {
            if (s_Instance != null) return s_Instance;

            s_Instance = FindObjectOfType();
            if (s_Instance == null)
            {
                var go = new GameObject(typeof(T).Name);
                s_Instance = go.AddComponent();
            }

            return s_Instance;
        }
    }

    public static void DestroyInstance()
    {
        DestroyImmediate(s_Instance);
        s_Instance = null;
    }
}

//---------------------------------------------------------------------------------
//-- 以下是用法
public class MonoSingletonTest : MonoSingleton
{
    private void Awake()
    {
        if (this != instance)
        {
            DestroyImmediate(gameObject);
            return;
        }

        // do xxxx
        
    }

    private void OnDestroy()
    {
        if (this != instance)
        {
            return;
        }

        // do xxxx
    }
}

下面简单进行说明.

首先类的描述:

public class MonoSingleton : MonoBehaviour where T : MonoBehaviour

声明一个泛型类MonoSingleton, 它继承于MonoBehaviour, 并且对该类进行约束: 它所持有的泛型类型必须也是继承MonoBehaviour.

之所有用泛型类而不是普通的类, 是因为我们需要返回的实例是Mono脚本本身而不是一个父类MonoSingleton.

然后是单例类的用法, 很简单, 继承MonoSingleton, 然后传入自身作为泛型持有类即可.

最后也提供了一个单例的销毁方法.

单例本身的初始化和销毁等可以放入Mono脚本的生命周期, 如Awake, Start, Destroy等.

你可能感兴趣的:(Unity,设计模式,游戏开发,unity)