今天来讲下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> : MonoBehaviourwhere 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是单线程,但为了保持代码一致性,所以不做区分。