一般在项目中, 我们会使用一些全局的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)来保证线程安全, 我们将在另外的文章中分享.
什么是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等.