简单委托
委托即是一种函数指针。
这里简单委托指的是,将委托的变量声明和注册都放在热更工程内进行。
创建委托
委托创建的位置在主工程和热更工程内都可以,我们这里放在主工程内
在ILRuntimeManager
内新添加两个委托,放在最外层即可
1 2
| public delegate void TestDelegateMethod(int a); public delegate string TestDelegateFunction(int a);
|
委托变量声明和注册简单委托
我们先在热更工程中新建一个类,命名为TestDelegate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| using UnityEngine;
namespace HotFix { public class TestDelegate { private TestDelegateMethod m_Method; private TestDelegateFunction m_Function;
public void Initialize() { m_Method = MyMethod; m_Function = MyFunction; }
public void HotDeleInvoker(int a) { if (m_Method != null) { m_Method.Invoke(a); }
if (m_Function != null) { m_Function.Invoke(a); } } void MyMethod(int a) { Debug.LogFormat("TestDelegate Method a={0}",a); } string MyFunction(int a) { string result = a.ToString(); Debug.LogFormat("TestDelegate Function result={0}",result); return result; } } }
|
修改主工程ILRuntimeManager
的OnHotFixLoaded
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void OnHotFixLoaded() {
IType TestDeleClassType = m_AppDomain.LoadedTypes["HotFix.TestDelegate"];
object obj = ((ILType)TestDeleClassType).Instantiate();
IMethod registerMethod = TestDeleClassType.GetMethod("Initialize", null, null); m_AppDomain.Invoke(registerMethod, obj,null);
IType intType = m_AppDomain.GetType(typeof(int)); List<IType> paramList = new List<IType>() { intType}; IMethod invokerMethod = TestDeleClassType.GetMethod("HotDeleInvoker",paramList,null); m_AppDomain.Invoke(invokerMethod, obj,13); }
|
跨域委托
跨域委托指的是,委托变量声明和注册不在同一个工程内
我们把委托变量的声明放在Unity主工程内,而把这些变量的注册放在热更工程内。
一般情况下,Unity主工程是得不到热更工程声明的委托的,更不能注册。所以把委托变量声明在Unity主工程内,注册放在热更工程内。
修改ILRuntimeManager
,添加委托变量
1 2 3 4 5 6 7 8 9
| public delegate void TestDelegateMethod(int a); public delegate string TestDelegateFunction(int a); public class ILRuntimeManager : SingletonPattern<ILRuntimeManager> { public TestDelegateMethod TestDeleMethod; public TestDelegateFunction TestDeleFunction; public System.Action<string> TestDeleAction;
}
|
热更工程注册和调用委托
给HotFix工程的TestDelegate
添加如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public void Initialize2() { ILRuntimeManager.Instance.TestDeleMethod = MyMethod; ILRuntimeManager.Instance.TestDeleFunction = MyFunction; ILRuntimeManager.Instance.TestDeleAction = MyAction; } public void HotDeleInvoker2(int a) { if (ILRuntimeManager.Instance.TestDeleMethod != null) { ILRuntimeManager.Instance.TestDeleMethod.Invoke(a); }
if (ILRuntimeManager.Instance.TestDeleFunction != null) { ILRuntimeManager.Instance.TestDeleFunction.Invoke(a); }
if (ILRuntimeManager.Instance.TestDeleAction != null) { ILRuntimeManager.Instance.TestDeleAction.Invoke("ATAO"); } } void MyAction(string a) { Debug.LogFormat("TestDelegate Action result={0}",a); }
|
修改ILRuntimeManager
的OnHotFixLoaded
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void OnHotFixLoaded() {
IType TestDeleClassType = m_AppDomain.LoadedTypes["HotFix.TestDelegate"];
object obj = ((ILType)TestDeleClassType).Instantiate();
IMethod registerMethod = TestDeleClassType.GetMethod("Initialize2", null, null); m_AppDomain.Invoke(registerMethod, obj,null);
IType intType = m_AppDomain.GetType(typeof(int)); List<IType> paramList = new List<IType>() { intType}; IMethod invokerMethod = TestDeleClassType.GetMethod("HotDeleInvoker2",paramList,null); m_AppDomain.Invoke(invokerMethod, obj,13); }
|
跨域委托适配器
上面的代码直接运行是不行的,想要在ILRuntime成功注册主工程的委托,需要自己写“委托适配器”
使用系统自带的Action、Func时,委托适配器很好写
我们在ILRuntimeManager
的InitializeILRuntime
方法内添加对应Action<string>
的委托适配器
1 2 3 4 5 6
| void InitializeILRuntime() { m_AppDomain.DelegateManager.RegisterMethodDelegate<string>(); }
|
直接调用AppDomain.DelegateManager.RegisterMethodDelegate<T>()
,其中的“T”可以指定这个Action的参数类型,在这里我们使用的是string
跨域委托转换器
使用自定义的Delegate时,我们需要自己将它转化为系统的Action或Func的形式
我们在ILRuntimeManager
的InitializeILRuntime
方法内添加对应TestDeleMethod
和TestDeleFunction
的委托转换器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void InitializeILRuntime() { m_AppDomain.DelegateManager.RegisterMethodDelegate<string>();
m_AppDomain.DelegateManager.RegisterMethodDelegate<int>(); m_AppDomain.DelegateManager.RegisterDelegateConvertor<TestDelegateMethod>(action => { return new TestDelegateMethod((a) => { ((System.Action<int>)action).Invoke(a); }); }); m_AppDomain.DelegateManager.RegisterFunctionDelegate<int, string>(); m_AppDomain.DelegateManager.RegisterDelegateConvertor<TestDelegateFunction>(action => { return new TestDelegateFunction((a) => { return ((System.Func<int,string>)action).Invoke(a); }); }); }
|
Unity自己包装好的委托,如UnityAction
也要自己写转换器进行转换
自定义委托转换器的声明是这样的:
1
| public void RegisterDelegateConvertor<T>(Func<Delegate, Delegate> action)
|
泛型T用于检测类型,传入的泛型参数必须属于System.Delegate
参数action属于Func<Delegate, Delegate>
,前一个Delegate是实现了自定义Delegate的函数的指针,后一个Delegate是进行转化后ILRuntime能用的Delegate的函数的指针。相当于告诉ILRuntime,当热更工程需要注册一个出现在主工程的委托时,使用这个适配器提供的方法对主工程的委托进行转换。
转换器是转换器,适配器是适配器,我们想要成功注册自定义委托,还需要再在每个转换器前面添加对应的适配器
跨域委托注意事项
- 相同参数类型的委托,不论是自定义委托还是Action、Func,只需要添加一次委托适配器即可
- 尽量避免不必要的跨域委托调用
- 尽量使用Action和Func
跨域委托补充
前面说到,UnityAction
也要自己写转换器进行转换,这里补充一些
比如UGUI的Toggle组件,使用的是UnityAction<bool>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void InitializeILRuntime() {
m_AppDomain.DelegateManager.RegisterMethodDelegate<bool>(); m_AppDomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction<bool>>(action => { return new UnityEngine.Events.UnityAction<bool>((a) => { ((System.Action<bool>)action).Invoke(a); }); }); }
|
在实际使用中,m_AppDomain.DelegateManager.RegisterMethodDelegate
和m_AppDomain.DelegateManager.RegisterFunctionDelegate
都会写在最前面,适配器写在一块儿能比较清楚地看到有没有重复的。
虽然跨域委托的调用我们写在了热更工程里,不过我们写在主工程里也是一样的,重点是在委托注册的时候注意跨域的问题。