问题:通过携程向服务器请求数据后,通过闭包形式给控件赋值,此时如果控件优先于赋值方程完成前被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;//赋值
}
});
}