前言
在我的上一篇文章里面使用反射实现了一个消息驱动组件:https://blog.csdn.net/qq_16054639/article/details/79579546
之所以要用反射而不用委托去实现广播消息系统是因为:
1. 反射比委托更加灵活(不定参数,空引用判断)
2. 效率上并不会比委托差多少(如果项目不能接受那就另说)
3. 可以做出更好方便使用的框架(今天的主题)
游戏通常的架构
分层设计+消息驱动+控制反转
设计实现
废话不多说先上代码
- 广播核心类
#define DEBUG
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace MiniCoco
{
public class MiniBroadCast
{
private static MiniBroadCast Instance = new MiniBroadCast();
static private MessengerHelper messengerHelper = (new GameObject("MessengerHelper")).AddComponent<MessengerHelper>();
private SafeDictionary<string, List<Observer>> m_observer = new SafeDictionary<string, List<Observer>>();
/**
* 注册观察者
*/
public static void RegistListener(System.Object o)
{
Type _t = o.GetType();
foreach (MethodInfo _method in _t.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
{
System.Object[] _attrs = _method.GetCustomAttributes(typeof(MiniUrl), false); //反射获得用户自定义属性
Observer _observer = new Observer();
///多个特性取最后一个
foreach (System.Attribute _attr in _attrs)
{
if (_attr is MiniUrl)
{
MiniUrl _mini_url = _attr as MiniUrl;
if (_mini_url.Message != null)
{
_observer.message = _mini_url.Message;
_observer.method = _method;
_observer.instance = o;
_observer.is_static = false;
_observer.object_name = o.ToString();
}
}
}
if (_observer.message != null)
{
#if DEBUG
Debug.Log("instance regist Url:" + _observer.message + " Method:" + _method.Name);
#endif
if (!MiniBroadCast.Instance.m_observer.ContainsKey(_observer.message))
MiniBroadCast.Instance.m_observer[_observer.message] = new List<Observer>();
MiniBroadCast.Instance.m_observer[_observer.message].Add(_observer);
}
}
}
/**
* 注册静态观察者
*/
public static void RegistListener(System.Type t)
{
foreach (MethodInfo _method in t.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static))
{
System.Object[] _attrs = _method.GetCustomAttributes(typeof(MiniUrl), false); //反射获得用户自定义属性
Observer _observer = new Observer();
///多个特性取最后一个
foreach (System.Attribute _attr in _attrs)
{
if (_attr is MiniUrl)
{
MiniUrl _mini_url = _attr as MiniUrl;
if (_mini_url.Message != null)
{
_observer.method = _method;
_observer.message = _mini_url.Message;
_observer.instance = null;
_observer.is_static = true;
}
}
}
if (_observer.message != null)
{
#if DEBUG
Debug.Log("static regist Url:" + _observer.message + " Method:" + _method.Name);
#endif
if (!MiniBroadCast.Instance.m_observer.ContainsKey(_observer.message))
MiniBroadCast.Instance.m_observer[_observer.message] = new List<Observer>();
MiniBroadCast.Instance.m_observer[_observer.message].Add(_observer);
}
}
}
/**
* p_params:不定参数
*/
public static void Broadcast(string key, params object[] p_params)
{
if (!MiniBroadCast.Instance.m_observer.ContainsKey(key))
{
#if DEBUG
Debug.LogError("observer not find");
#endif
return;
}
//反射调用(包括静态方法)
List<Observer> _list = new List<Observer>();
//深度复制一份,避免在反射执行过程中删除观察者出现错误
for (int i = MiniBroadCast.Instance.m_observer[key].Count - 1; i >= 0; i--)
{
if (!MiniBroadCast.Instance.m_observer[key][i].is_static && MiniBroadCast.Instance.m_observer[key][i].instance.Equals(null))
{
#if DEBUG
Debug.LogWarning("null is " + MiniBroadCast.Instance.m_observer[key][i].object_name);
#endif
MiniBroadCast.Instance.m_observer[key].RemoveAt(i);
if (MiniBroadCast.Instance.m_observer[key].Count == 0)
MiniBroadCast.Instance.m_observer.Remove(key);
}
else
{
_list.Add(MiniBroadCast.Instance.m_observer[key][i]);
}
}
//执行方法
foreach (Observer o in _list)
{
o.method.Invoke(o.instance, p_params);
}
}
public static void RemoveListener(System.Object o)
{
Type _t = o.GetType();
foreach (MethodInfo _method in _t.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
{
System.Object[] _attrs = _method.GetCustomAttributes(typeof(MiniUrl), false); //反射获得用户自定义属性
Observer _observer = new Observer();
string _message="";
///多个特性取最后一个
foreach (System.Attribute _attr in _attrs)
{
if (_attr is MiniUrl)
{
MiniUrl _mini_url = _attr as MiniUrl;
if (_mini_url.Message != null)
{
_message = _mini_url.Message;
}
}
}
if (!MiniBroadCast.Instance.m_observer.ContainsKey(_message))
continue;
List<Observer> _list = MiniBroadCast.Instance.m_observer[_message];
for (int i = _list.Count - 1; i >= 0; i--)
{
if (_list[i].instance == o && _list[i].method.Name == _method.Name)
{
_list.RemoveAt(i);
#if DEBUG
Debug.Log("Remove Instance:" + o.ToString() + " Method:" + _method.Name);
#endif
if (_list.Count == 0)
MiniBroadCast.Instance.m_observer.Remove(_message);
}
}
if (_list.Count == 0)
MiniBroadCast.Instance.m_observer.Remove(_message);
}
}
public static void RemoveListener(System.Type t)
{
foreach (MethodInfo _method in t.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static))
{
System.Object[] _attrs = _method.GetCustomAttributes(typeof(MiniUrl), false); //反射获得用户自定义属性
Observer _observer = new Observer();
string _message = "";
///多个特性取最后一个
foreach (System.Attribute _attr in _attrs)
{
if (_attr is MiniUrl)
{
MiniUrl _mini_url = _attr as MiniUrl;
if (_mini_url.Message != null)
{
_message = _mini_url.Message;
}
}
}
if (!MiniBroadCast.Instance.m_observer.ContainsKey(_message))
continue;
List<Observer> _list = MiniBroadCast.Instance.m_observer[_message];
for (int i = _list.Count - 1; i >= 0; i--)
{
if (_list[i].instance == null && _list[i].method.Name == _method.Name)
{
_list.RemoveAt(i);
#if DEBUG
Debug.Log("Remove Instance:null Method:" + _method.Name);
#endif
if (_list.Count == 0)
MiniBroadCast.Instance.m_observer.Remove(_message);
}
}
if (_list.Count == 0)
MiniBroadCast.Instance.m_observer.Remove(_message);
}
}
public static void CleanAll()
{
MiniBroadCast.Instance.m_observer = new SafeDictionary<string, List<Observer>>();
#if DEBUG
Debug.Log("clean all");
#endif
}
private class Observer
{
public string message;
public object instance;
public MethodInfo method;
public bool is_static;
public string object_name = "";
}
private sealed class MessengerHelper : MonoBehaviour
{
void Awake()
{
DontDestroyOnLoad(gameObject);
}
//Clean up eventTable every time a new level loads.
public void OnLevelWasLoaded(int unused)
{
MiniBroadCast.CleanAll();
}
}
}
}
相比于上一个实现,这次使用了特征+反射的方式,自动反射当前需要注册的方法.用法上显得更加轻便了有木有,另外加入了切换关卡自动清空字典的功能,这个问题其实在上一个版本里面就有发现,当时处理的是一旦发现广播中存在空引用就移除这个Observer.加入自动清除会使代码效率高一点也使代码更加的健壮.
- 特性类
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MiniCoco{
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false,Inherited=false)]
public class MiniUrl : Attribute
{
public string Message { get; set; }
}
}
- 线程安全字典
using System.Collections;
using System.Collections.Generic;
namespace MiniCoco
{
public class SafeDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private readonly object syncRoot = new object();
private readonly Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();
#region IDictionary<TKey,TValue> Members
/// <summary>
/// Adds an element with the provided key and value to the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
/// </summary>
/// <param name="key">The object to use as the key of the element to add.</param>
/// <param name="value">The object to use as the value of the element to add.</param>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IDictionary`2"></see> is read-only.</exception>
/// <exception cref="T:System.ArgumentException">An element with the same key already exists in the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.</exception>
/// <exception cref="T:System.ArgumentNullException">key is null.</exception>
public void Add(TKey key, TValue value)
{
lock (syncRoot)
{
d.Add(key, value);
}
}
/// <summary>
/// Determines whether the <see cref="T:System.Collections.Generic.IDictionary`2"></see> contains an element with the specified key.
/// </summary>
/// <param name="key">The key to locate in the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.</param>
/// <returns>
/// true if the <see cref="T:System.Collections.Generic.IDictionary`2"></see> contains an element with the key; otherwise, false.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">key is null.</exception>
public bool ContainsKey(TKey key)
{
return d.ContainsKey(key);
}
/// <summary>
/// Gets an <see cref="T:System.Collections.Generic.ICollection`1"></see> containing the keys of the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
/// </summary>
/// <value></value>
/// <returns>An <see cref="T:System.Collections.Generic.ICollection`1"></see> containing the keys of the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"></see>.</returns>
public ICollection<TKey> Keys
{
get
{
lock (syncRoot)
{
return d.Keys;
}
}
}
/// <summary>
/// Removes the element with the specified key from the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
/// </summary>
/// <param name="key">The key of the element to remove.</param>
/// <returns>
/// true if the element is successfully removed; otherwise, false. This method also returns false if key was not found in the original <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
/// </returns>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IDictionary`2"></see> is read-only.</exception>
/// <exception cref="T:System.ArgumentNullException">key is null.</exception>
public bool Remove(TKey key)
{
lock (syncRoot)
{
return d.Remove(key);
}
}
/// <summary>
/// Tries the get value.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <returns></returns>
public bool TryGetValue(TKey key, out TValue value)
{
lock (syncRoot)
{
return d.TryGetValue(key, out value);
}
}
/// <summary>
/// Gets an <see cref="T:System.Collections.Generic.ICollection`1"></see> containing the values in the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
/// </summary>
/// <value></value>
/// <returns>An <see cref="T:System.Collections.Generic.ICollection`1"></see> containing the values in the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"></see>.</returns>
public ICollection<TValue> Values
{
get
{
lock (syncRoot)
{
return d.Values;
}
}
}
/// <summary>
/// Gets or sets the <see cref="TValue"/> with the specified key.
/// </summary>
/// <value></value>
public TValue this[TKey key]
{
get { return d[key]; }
set
{
lock (syncRoot)
{
d[key] = value;
}
}
}
/// <summary>
/// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
/// </summary>
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.</exception>
public void Add(KeyValuePair<TKey, TValue> item)
{
lock (syncRoot)
{
((ICollection<KeyValuePair<TKey, TValue>>)d).Add(item);
}
}
/// <summary>
/// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
/// </summary>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only. </exception>
public void Clear()
{
lock (syncRoot)
{
d.Clear();
}
}
/// <summary>
/// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> contains a specific value.
/// </summary>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
/// <returns>
/// true if item is found in the <see cref="T:System.Collections.Generic.ICollection`1"></see>; otherwise, false.
/// </returns>
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return ((ICollection<KeyValuePair<TKey, TValue>>)d).Contains(item);
}
/// <summary>
/// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"></see> to an <see cref="T:System.Array"></see>, starting at a particular <see cref="T:System.Array"></see> index.
/// </summary>
/// <param name="array">The one-dimensional <see cref="T:System.Array"></see> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"></see>. The <see cref="T:System.Array"></see> must have zero-based indexing.</param>
/// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException">arrayIndex is less than 0.</exception>
/// <exception cref="T:System.ArgumentNullException">array is null.</exception>
/// <exception cref="T:System.ArgumentException">array is multidimensional.-or-arrayIndex is equal to or greater than the length of array.-or-The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"></see> is greater than the available space from arrayIndex to the end of the destination array.-or-Type T cannot be cast automatically to the type of the destination array.</exception>
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
lock (syncRoot)
{
((ICollection<KeyValuePair<TKey, TValue>>)d).CopyTo(array, arrayIndex);
}
}
/// <summary>
/// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
/// </summary>
/// <value></value>
/// <returns>The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</returns>
public int Count
{
get { return d.Count; }
}
/// <summary>
/// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.
/// </summary>
/// <value></value>
/// <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only; otherwise, false.</returns>
public bool IsReadOnly
{
get { return false; }
}
/// <summary>
/// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
/// </summary>
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
/// <returns>
/// true if item was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"></see>; otherwise, false. This method also returns false if item is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"></see>.
/// </returns>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.</exception>
public bool Remove(KeyValuePair<TKey, TValue> item)
{
lock (syncRoot)
{
return ((ICollection<KeyValuePair<TKey, TValue>>)d).Remove(item);
}
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection.
/// </returns>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return ((ICollection<KeyValuePair<TKey, TValue>>)d).GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)d).GetEnumerator();
}
#endregion
}
}
因为考虑到多线程安全问题,又不能使用ConcurrentDictionary,无奈封装了个线程安全字典.
- 实验代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MessageConst {
public const string TEST = "test";
public const string TEST_2 = "other";
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MiniCoco;
using UnityEngine.SceneManagement;
public class Test : MonoBehaviour {
// Use this for initialization
void Start () {
//注册实例方法
MiniBroadCast.RegistListener(this);
//注册静态方法
MiniBroadCast.RegistListener(typeof(Test));
}
// Update is called once per frame
void Update () {
}
[MiniUrl(Message = MessageConst.TEST)]
void Test1()
{
Debug.Log("test1 get message!");
MiniBroadCast.RemoveListener(this);
}
public void OnClick()
{
//发送消息
MiniBroadCast.Broadcast(MessageConst.TEST);
}
[MiniUrl(Message = MessageConst.TEST)]
static void Test2()
{
Debug.Log("test2 get message!");
MiniBroadCast.RemoveListener(typeof(Test));
}
}
优点
- 省去了先前的一堆消息注册代码
- 消息就在对应的函数头顶上,以后阅读起来方便
注意事项
- 自己特别要控住好方法的参数个数,广播和接受参数个数要对应
结语
欢迎在留言区跟我进行讨论,指出我的不足
我也在学习的途中,希望我的总结能帮助到正在学习的小伙伴
欢迎转载,转载请注明出处