今天来讲下Unity中泛型单例的使用,包含普通单例和继承MonoBehaviour的单例。重点是需要两种泛型单例兼容WebGL平台,话不多说直接开始。
泛型单例的设计目标
作为泛型单例,需要实现以下几个目标:
- 全局唯一,在程序的整个运行周期内有且只有一个实例。
- 全局调用,这属于单例的特性,方便任意调用。
- 继承即用,通过继承泛型单基类例即可实现单例特性
WebGL兼容问题
由于泛型单例要防止外部创建,构造函数会被定义为private或protected,因而对象需要通过反射的方式来创建。由于WebGL采用的是AOT的编译方式(Ahead-of-Time Compilation,提前编译),所以对反射的使用有很多的限制。
泛型单例的实现方式
普通泛型单例的实现,示例代码如下:
public abstract class Singleton<T> where T : Singleton<T>
{
private static readonly Lazy<T> mLazyInstance = new Lazy<T>(Constructor, LazyThreadSafetyMode.ExecutionAndPublication);
public static T Instance => mLazyInstance.Value;
private static T Constructor()
{
ConstructorInfo constructor = typeof(T).GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null, Type.EmptyTypes, null);
if (constructor == null)
{
throw new Exception($"No private/protected constructor found for {typeof(T).Name}");
}
return (T)constructor.Invoke(null);
}
}
MonoBehaviour泛型单例的实现,示例代码如下:
public abstract class SingletonMonoBehaviour<T> : MonoBehaviour
where T : SingletonMonoBehaviour<T>
{
private static readonly Lazy<T> mLazyInstance = new Lazy<T>(Constructor, LazyThreadSafetyMode.ExecutionAndPublication);
public static T Instance => mLazyInstance.Value;
private static T Constructor()
{
string path = SingletonPath;
GameObject aimGameObject = UtilCollection.GameObjectUtil.FindOrCreateGameObject(path);
T instance = aimGameObject.GetComponent<T>();
if (instance == null)
instance = aimGameObject.AddComponent<T>();
return instance;
}
/// <summary>防止重复动态创建</summary>
private void Awake()
{
StartCoroutine(CheckRepeatCreate());
}
/// <summary>防止重复挂接脚本</summary>
private void Reset()
{
StartCoroutine(CheckRepeatCreate());
}
private IEnumerator CheckRepeatCreate()
{
GameObject aimGameObject = GameObject.Find(SingletonPath);
if (this.gameObject != aimGameObject)
{
yield return null;
if (this != null)
DestroyImmediate(this);
}
else if (this.gameObject == aimGameObject)
{
T[] components = aimGameObject.GetComponents<T>();
if (components.Length > 1)
{
yield return null;
if (this != null)
DestroyImmediate(this);
}
}
}
}
示例中用Lazy<T>类型作为实例类型,Lazy<T>可以通过ExecutionAndPublication参数确保多线程安全,在多线程情况下同样只会有一个实例。虽然WebGL是单线程,但为了保持代码一致性,所以不做区分。