Unity中有很多特別的類需要以單例模式呈現,比如全局的UI管理類,各種緩存池,以及新手導航類等等。而Unity中,因為所有繼承自Monobehaviour的腳本在實現的時候都是單線程的,所以像網上流傳的一些C#的實現方式就顯得不那么的實用了。
很多國內的公司所使用的MonoSingleton都是有問題的,比如像Easytouch中關於單例是這樣實現中有這樣一段代碼。
public static T instance { get { if (m_Instance == null) { m_Instance = GameObject.FindObjectOfType(typeof(T)) as T;//1這里耗費性能,有風險 if (m_Instance == null)//2 { m_Instance = new GameObject("Singleton of " + typeof(T).ToString(), typeof(T)).GetComponent<T>(); m_Instance.Init(); } } return m_Instance; } }
那么我標注的兩處就是代碼當中不正確的地方。2處這是明顯的套用了多線程的單例實現方式,而實際上,在單線程模式當中這個判斷並沒有意義。而1中,直接對全場景進行搜索的過程其本身就很浪費性能。那么正確的實現方式是什么呢?
首先,我們需要一個全局變量,比如,先建立一個全局類Global
public abstract class Global : MonoBehaviour { public static HashSet<string> Singleton=new HashSet<string>(); }
每次建立都將類名存進HashSet當中,那么上面那段代碼就可以改成這樣。
public static T instance { get { if (m_Instance == null) { var name = "Singleton of " + typeof(T).ToString(); var flag = Global.Singleton.Contains(name); if (!flag) { m_Instance = new GameObject(name, typeof(T)).GetComponent<T>(); m_Instance.Init(); Global.Singleton.Add(name); } } return m_Instance; } }
可能您要說了,我已經有了一個全局類了,那么難道還要再填一個東西?我只想直接用,用沒有更簡便的方法。您要說更好,不一定,但是更簡便,確實有的。我們這里可以用上互斥類Mutex的類,那么上面那段代碼就可以改成下面這樣:
public static T instance { get { if (m_Instance == null)//注意,此處在實際中只執行一次。 { bool createdNew; var name = "Singleton of " + typeof(T).ToString(); Mutex mutex = new Mutex(false, name, out createdNew); if (createdNew) { m_Instance = new GameObject(name, typeof(T)).GetComponent<T>(); m_Instance.Init(); } } return m_Instance; } }
但這只是說,我如果在其他地方操作這個單例,而這個單例還必須新建一個游戲物體,還必須掛在上面,掛在游戲物體上就至少要有一個transform組件。那么我可不可以直接掛在物體上,那該怎么辦?如果我掛多了該怎么辦?
有辦法,這里我們利用Awake()方法
private void Awake() { if (m_Instance == null) { bool createdNew; var name = "Singleton of " + typeof(T).ToString(); Mutex mutex = new Mutex(false, name, out createdNew); if (createdNew) { m_Instance = this as T; m_Instance.Init(); } } else { Destroy(this); } }
這樣就可以保證運行時的單例了。那么完整的MonoSingleton還需要一些細節。比如在我的單例基類中,我設計成抽象類,提供了兩個抽象函數,分別是初始化和逆初始化。之所以這么做就是為了在我們繼承MonoSingleton的時候想一想,是不是必須要把這個類做成單例的,它一定是有單例的必要所以才是單例的,而不是將單例當靜態使用。
using UnityEngine; using System.Collections; using System.Threading; /// <summary> /// 單例基類,提供兩個抽象函數Init 和 DisInit 初始化和逆初始化過程。 /// </summary> /// <typeparam name="T"></typeparam> public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T> { private static T m_Instance = null; private static string name; private static Mutex mutex; public static T instance { get { if (m_Instance == null) { if ( IsSingle()) { m_Instance = new GameObject(name, typeof(T)).GetComponent<T>(); m_Instance.Init(); } } return m_Instance; } } private static bool IsSingle() { bool createdNew; name = "Singleton of " + typeof(T).ToString(); mutex = new Mutex(false, name, out createdNew); return createdNew; } private void Awake() { if (m_Instance == null) { if (IsSingle()) { m_Instance = this as T; m_Instance.Init(); } } else { Destroy(this); } } protected abstract void Init(); protected abstract void DisInit(); private void OnDestory() { if (m_Instance!=null) { mutex.ReleaseMutex(); DisInit(); m_Instance = null; } } private void OnApplicationQuit() { mutex.ReleaseMutex(); } }