Unity 游戏框架搭建 2019 (四十八/四十九) MonoBehaviourSimplify 中的消息策略完善&关于发送事件的简单封装

先贴出来代码:

using System; using System.Collections.Generic; namespace QFramework { public abstract partial class MonoBehaviourSimplify { Dictionary<string, Action<object>> mMsgRegisterRecorder = new Dictionary<string, Action<object>>(); protected void RegisterMsg(string msgName, Action<object> onMsgReceived) { MsgDispatcher.Register(msgName, onMsgReceived); mMsgRegisterRecorder.Add(msgName, onMsgReceived); } private void OnDestroy() { OnBeforeDestroy(); foreach (var keyValuePair in mMsgRegisterRecorder) { MsgDispatcher.UnRegister(keyValuePair.Key,keyValuePair.Value); } mMsgRegisterRecorder.Clear(); } protected abstract void OnBeforeDestroy(); } public class B : MonoBehaviourSimplify { private void Awake() { RegisterMsg("Do", DoSomething); RegisterMsg("DO1", _ => { }); RegisterMsg("DO2", _ => { }); RegisterMsg("DO3", _ => { }); } void DoSomething(object data) { // do something } protected override void OnBeforeDestroy() { } } }

我们是使用字典进行注册消息的记录的,使用字典就要保证字典中的 key 是唯一的。而我们很可能在一个脚本中对一个关键字注册多次,这样用字典这个数据结构就显得不合理了。

相比字典,List 更合适,因为我们有有可能有重复的内容,而字典更适合做一些查询工作,但是 List 并不支持键值对,怎么办呢?

我们只好创建一个结构来存储我们的消息名和对应的委托,这个结构是一个类叫做 MsgRecord

消息策略部分的代码如下:

public abstract partial class MonoBehaviourSimplify { List<MsgRecord> mMsgRecorder = new List<MsgRecord>(); private class MsgRecord { public string Name; public Action<object> OnMsgReceived; } protected void RegisterMsg(string msgName, Action<object> onMsgReceived) { MsgDispatcher.Register(msgName, onMsgReceived); mMsgRecorder.Add(new MsgRecord { Name = msgName, OnMsgReceived = onMsgReceived }); } private void OnDestroy() { OnBeforeDestroy(); foreach (var msgRecord in mMsgRecorder) { MsgDispatcher.UnRegister(msgRecord.Name,msgRecord.OnMsgReceived); } mMsgRecorder.Clear(); } protected abstract void OnBeforeDestroy(); }

代码比较简单。

而我们的示例代码,如下,增加了一行重复注册的代码。

public class B : MonoBehaviourSimplify { private void Awake() { RegisterMsg("Do", DoSomething); RegisterMsg("Do", DoSomething); RegisterMsg("DO1", _ => { }); RegisterMsg("DO2", _ => { }); RegisterMsg("DO3", _ => { }); } void DoSomething(object data) { // do something } protected override void OnBeforeDestroy() { } }

而我们的 MonoBehaviourSimplify 内部实现发生了天翻地覆的变化,也没有对我们的示例代码产生一点影响,这叫封装。

那么到这里,我们的消息策略还有问题吗?

还有的,问题在创建 MsgRecord 的部分。
如下:

mMsgRecorder.Add(new MsgRecord { Name = msgName, OnMsgReceived = onMsgReceived });

我们每次注册消息,都要 new 一个 MsgRecord 对象出来,而我们在注销的时候,对这个对象是什么都没有做的,注销的代码如下:

foreach (var msgRecord in mMsgRecorder) { MsgDispatcher.UnRegister(msgRecord.Name,msgRecord.OnMsgReceived); }

这样会造成一个性能问题,这个性能问题主要是有 new 时候寻址造成的,具体原因自行搜索,当然在本专栏的后边还是会介绍的。我们要做的,就是减少 new 的发生次数,要想减少,就得让我们的 MsgRecord 能够回收利用。

如何回收利用呢,答案是维护一个容器,比如 List 或者 Queue、Stack 等,也就是传说中的对象池。由于我们的 MsgRecord 的作用仅仅是作为一个存储结构而已,而存储的顺序也不是很重要,所以我们就用做简单的 Stack 结构,也就是栈,来作为 MsgRecord 对象池的容器。

其实现如下:

private class MsgRecord { static Stack<MsgRecord> mMsgRecordPool = new Stack<MsgRecord>(); public static MsgRecord Allocate() { if (mMsgRecordPool.Count > 0) { return mMsgRecordPool.Pop(); } return new MsgRecord(); } public void Recycle() { Name = null; OnMsgReceived = null; mMsgRecordPool.Push(this); } public string Name; public Action<object> OnMsgReceived; }

由于这个对象池只给 MsgRecord 用,所以就在 MsgRecord 内部实现了。
Allocate 是申请,也就是获取对象。Recycle 就是回收,当不用的时候调用一下就好了。

原理很简单。而 mMsgRecordPool 之所以设置成了 private 访问权限,是因为,不希望被外部访问到。对于一个类的设计来讲,MsgRecord 是一个非常合格的类了。

应用到我们的消息策略的代码如下:

protected void RegisterMsg(string msgName, Action<object> onMsgReceived) { MsgDispatcher.Register(msgName, onMsgReceived); // var msgRecord = MsgRecord.Allocate(); msgRecord.Name = msgName; msgRecord.OnMsgReceived = onMsgReceived; mMsgRecorder.Add(msgRecord); } private void OnDestroy() { OnBeforeDestroy(); foreach (var msgRecord in mMsgRecorder) { MsgDispatcher.UnRegister(msgRecord.Name,msgRecord.OnMsgReceived); // msgRecord.Recycle(); } mMsgRecorder.Clear(); }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wspgfz.html