天天看点

Unity3d 控件被赋值前因控件被删除而导致的访问空对象错误

问题:通过携程向服务器请求数据后,通过闭包形式给控件赋值,此时如果控件优先于赋值方程完成前被destroy,会报错“尝试给一个被destroy物体赋值”,次问题可能引起软件崩溃(特别是移动端)

示例代码如下:

private void GetBalance()//调用方程
{
    for (int i = 0; i < m_curItems.Count; i++) //m_curItems存储了一堆控件对象
	{
	    m_curItems[i].getBalance();//调取控件对象自己的函数
	}
}


[SerializeField]private Text balance; //控件绑定
public void getBalance()//请求数据,闭包内赋值
{
    //此SDK数据请求方式为协程
	MarvelSDK.SDK.API.GetETHBalance(m_detail.addr, (error,x) =>
	{
		if (x != null)
		{
			balance.text = x.balance;//赋值
		}
	});
}
           

解决思路:

1.    控件删除时,停止所有协程        StopAllCoroutines();

2.    添加判断条件,判断控件是否还存在,如果存在则赋值,不存在则不进行操作

尝试debug时所遇到的问题:

1.    虽然停止了数据请求方程的协程,但是由于采用闭包方式在给控件赋值,因闭包特性,所以闭包内赋值方程依旧会执行,所以思路1不好使

2.    起先添加判断条件,尝试通过判断此控件是否还存在于场景中来控制赋值方程的运作

 if(balance.gameObject.activeInHierarchy)
           

但此时犯了一个判断错误,”activeInHierarchy“是用来判断控件是否在场景中是否可见,但如果控件被destroy了,那么此时此方法也会报“尝试访问被destroy物体”的错误。

由此,引出今天的重点,换一个思维来判断控件是否被destroy。

首先,确定一个基础概念,一个控件被实例化之后,实际上是在内存内开辟了一段内存空间,我们对控件的操作实际上是对这段内存空间的操作,既然是对内存空间的操作,也就意味之我们需要一个内存指针,而此时我们的内存指针其实就是这个预制体绑定参数:

[SerializeField]private Text balance;//这货可以理解成一个指针,指向它所绑定的控件对象
           

而且我们知道unity3d中销毁函数Destroy()的功能是销毁实例,释放控件所绑定的内存区域,重点来了,既然这个控件内存区域已经被释放,那么指向它的函数指针也会被销毁,但由于我们我们在闭包中使用了这个指针,因闭包的特性,它会被闭包所生成的匿名类抓取,被匿名类所引用(任何一个函数或参数如果被其他对象所引用,那么他将不会被销毁,直到引用它的对象被销毁时才会一并销毁)所以依然存在,但由于它所指的控件被destroy了,所以此时这个函数指针会指向null。

所以综上所述,最终解决这个问题的方式是判断这个函数指针是否指向null, 代码如下:

private void GetBalance()//调用方程
{
	for (int i = 0; i < m_curItems.Count; i++)
	{
	    m_curItems[i].getBalance();
	}
}

[SerializeField]private Text balance; //指向控件的内存区域
public void getBalance()//请求数据,闭包内赋值
{
	MarvelSDK.SDK.API.GetETHBalance(m_detail.addr, (error,x) =>
	{
		if (x != null)
		{
		    if(balance == null	)//如果指向null,意味着控件被删除,则强制返回上层函数
			    return;
								
			balance.text = x.balance;//赋值
		}
    });
}
           

继续阅读