在咱们使用xLua做为Unity中lua集成的解决方案时,遇到了一个问题,就是当咱们使用在lua中把UI中的某个控件绑定相应的事件(如按钮的onClick事件),xLua绑定这个事件是用委托实现的,具体代码能够查看xLua的代码。而在程序退出的时候xLua会检查对应的委托有没有被正确的释放掉,若是没有释放掉的话就会抛出异常。代码如表所示:git
1 public virtual void Dispose(bool dispose) 2 { 3 #if THREAD_SAFE || HOTFIX_ENABLE 4 lock (luaEnvLock) 5 { 6 #endif 7 if (disposed) return; 8 Tick(); 9 10 if (!translator.AllDelegateBridgeReleased()) 11 { 12 throw new InvalidOperationException("try to dispose a LuaEnv with C# callback!"); 13 } 14 15 LuaAPI.lua_close(L); 16 17 ObjectTranslatorPool.Instance.Remove(L); 18 translator = null; 19 20 rawL = IntPtr.Zero; 21 22 disposed = true; 23 #if THREAD_SAFE || HOTFIX_ENABLE 24 } 25 #endif 26 }
这说明咱们并无把对应的委托给释放掉。因此咱们须要确保在程序退出以前全部的委托要正确地释放掉。方案大致以下,每个UI都对应一个实例,这样在绑定控件的时候建立一个匿名函数,这个函数用于控件把这个控件绑定的事件清除掉,同时把这个匿名函数放到一个数组里面去,在这个UI销毁的时候调用一个函数(好比咱们叫作Destroy),这个函数的做用就是负责一些清理工做,其中就包括遍历前面提到的匿名函数的数组并挨个调用。这样就把xLua生成的委托的引用去掉了。在程序退出并触发GC的时候就会把这个委托释放掉,这样xLua检查就没有问题了。github
1 function UIUtils:AddButtonOnClick(aUIInstance, aButton, aFunc) 2 aButton.onClick.AddListener( 3 function () 4 aFunc(aUIInstance) 5 end) 6 7 // 将闭包添加到一个table中用于后面调用 8 table.insert(aUIInstance.unregisterWidgetClousures, 9 function() 10 aButton.onClick:RemoveAllListeners() 11 end) 12 13 end
可能到这里你以为问题已经解决了,但是若是到这的话就不会有这篇文章了。问题是这样调用了之后在程序退出的时候仍是会抛出异常。按正常来讲这样作了就能够了,通过一番实验发现只要这个控件没有被触碰过那么就能够正常退出,若是触碰了就会抛出异常。一开始怀疑是xLua的问题但通过看代码肯定不是它的问题。这个时候想到了可能Unity对这个委托作了缓存,虽然我上面把它清除掉了,可是Unity内部多是作了缓存的。最开始没有去关注这个问题,而是想了另一个办法直接把控件对应的事件给黑窑了。示例代码以下所示:数组
1 function UIUtils:AddButtonOnClick(aUIInstance, aButton, aFunc) 2 aButton.onClick.AddListener( 3 function () 4 aFunc(aUIInstance) 5 end) 6 7 // 将闭包添加到一个table中用于后面调用 8 table.insert(aUIInstance.unregisterWidgetClousures, 9 function() 10 aButton.onClick = nil 11 end) 12 13 end
这样就解决了问题。可是后面发现咱们要重用UI的时候因为咱们重用的规则所致(UI的C#对象没有回收可是会回收,可是lua对象会回收),上面的这个地方就出问题了。当咱们下次再要从新使用这个UI的时候,由于上面被置空了,接下来使用就有问题了。咱们也想过其它的方法来解决,但总感受破坏了原有简单的结构。这样作不太好。这个时候就想看看Unity到底哪里出了问题了,不过幸运的是很快就发现了问题。咱们使用ILSpy打开UnityEngine.dll查看了一下UnityEvent的代码,发如今它的基类里面作了一个简单的优化,就是这个优化致使了上面问题的发生。咱们来看下代码片段: 缓存
1 public abstract class UnityEventBase : ISerializationCallbackReceiver 2 { 3 private InvokableCallList m_Calls; 4 }
Unity用这个来保存须要调用函数,咱们再来看看它的具体实现片断:闭包
1 namespace UnityEngine.Events 2 { 3 internal class InvokableCallList 4 { 5 private readonly List<BaseInvokableCall> m_PersistentCalls = new List<BaseInvokableCall>(); 6 7 private readonly List<BaseInvokableCall> m_RuntimeCalls = new List<BaseInvokableCall>(); 8 9 private readonly List<BaseInvokableCall> m_ExecutingCalls = new List<BaseInvokableCall>(); 10 11 private bool m_NeedsUpdate = true; 12 13 public void AddListener(BaseInvokableCall call) 14 { 15 this.m_RuntimeCalls.Add(call); 16 this.m_NeedsUpdate = true; 17 } 18 19 public void RemoveListener(object targetObj, MethodInfo method) 20 { 21 List<BaseInvokableCall> list = new List<BaseInvokableCall>(); 22 for (int i = 0; i < this.m_RuntimeCalls.Count; i++) 23 { 24 if (this.m_RuntimeCalls[i].Find(targetObj, method)) 25 { 26 list.Add(this.m_RuntimeCalls[i]); 27 } 28 } 29 this.m_RuntimeCalls.RemoveAll(new Predicate<BaseInvokableCall>(list.Contains)); 30 this.m_NeedsUpdate = true; 31 } 32 33 public void Clear() 34 { 35 this.m_RuntimeCalls.Clear(); 36 this.m_NeedsUpdate = true; 37 } 38 39 public void Invoke(object[] parameters) 40 { 41 if (this.m_NeedsUpdate) 42 { 43 this.m_ExecutingCalls.Clear(); 44 this.m_ExecutingCalls.AddRange(this.m_PersistentCalls); 45 this.m_ExecutingCalls.AddRange(this.m_RuntimeCalls); 46 this.m_NeedsUpdate = false; 47 } 48 for (int i = 0; i < this.m_ExecutingCalls.Count; i++) 49 { 50 this.m_ExecutingCalls[i].Invoke(parameters); 51 } 52 } 53 } 54 }
因而代码变成了以下代码示例的样子:函数
1 function UIUtils:AddButtonOnClick(aUIInstance, aButton, aFunc) 2 aButton.onClick.AddListener( 3 function () 4 aFunc(aUIInstance) 5 end) 6 7 // 将闭包添加到一个table中用于后面调用 8 table.insert(aUIInstance.unregisterWidgetClousures, 9 function() 10 aButton.onClick:RemoveAllListeners() 11 aButton.onClick() 12 end) 13 14 end
好的,到这里问题已经完美解决了。固然咱们也能够简单的把抛异常的地方注释掉,但这确定不是解决问题的正确方法。固然若是你也遇到这个问题而且有更好的方案也能够一块儿讨论。优化