今天在firebase后台发现一个数量蛮多的报错,大概长这样子:
一个空引用的问题,经过分析定位,还算比较顺利的推测出了问题。我的消息系统里有一个消息在一个对象上只注册却遗漏了注销。 这样该对象销毁的时候,再次发这个消息,就会造成报错。但是有趣的点在于,我发送消息的代码长这样:
其中IMessage是我消息类的接口,所有想要接受消息的类都需要继承这个接口
很明显的问题是:我判空了呀!就算我之前消息遗漏了注销,但是理应我这里会把空的排除掉,就进不去红框里那个函数呀。于是我开始模拟报错情况,并且打上了断点。
好!进来了,我们看到消息对象中很明显前两个元素是null,接下来往下走,直接走到该元素:
进来了!而且断点明显显示IMsg是”null“ 但是”无视“了这个判定走到了下一步,这是咋回事啊?
第一反应,这个断点里标的null是带双引号的,会不是其实他是一个字符串"null"?不过那也太离谱,我这个对象很明显不是字符串。是一个继承了这个消息接口的类:
我们看到这个物体已经被销毁了,所以这里这样提示是很合理的,但是这个东西为什么不是纯粹的NUll?
于是我猜想:我的消息对象列表记录了这些Mono脚本的引用,但是Mono脚本所依附的Gameobject被销毁了,但是Mono脚本的引用还在,就不会成功==null判定,而是变成了带双引号的"null"。
然后开始证实我的猜想,写了如下测试代码:
List<MergeItem> Tests = new List<MergeItem>();
private void Update()
{
if (Input.GetKeyDown(KeyCode.Alpha1))
{
//第一步 随便新建十个物体 挂上Mono脚本 MergeItem
for(int i = 0; i < 10; i++)
{
GameObject obj = new GameObject("TestMono"+i);
Tests.Add(obj.AddComponent<MergeItem>());
}
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
//第二步 随机销毁一个幸运儿 这里删除下标为5的 gameobj 注意要销毁的是gameobject不是脚本
DestroyImmediate(Tests[5].gameObject);
}
if (Input.GetKeyDown(KeyCode.Alpha3))
{
//第三步 依次打印出来看看输出啥
for(int i = 0; i < Tests.Count; i++)
{
IMessage mergeItem = Tests[i];
if(mergeItem!=null)
Debug.LogError(Tests[i] + " ");
}
}
}
然后再启动游戏,依次按下数字 1,2,3,结果如下:
幸运儿TestMono5已经被销毁了,但是打印没打印出来!也就是说他在mergeItem!=null这个判定被拦下来了,也就是说这样Gameobect销毁,他上边的脚本也是会乖乖变成null的,我的猜想是错的!
那是为什么呢?我想,难道是因为我消息系统里获取的是接口而不是直接获取的类?
于是改改测试代码,如下:
List<IMessage> Tests = new List<IMessage>();
private void Update()
{
if (Input.GetKeyDown(KeyCode.Alpha1))
{
//第一步 随便新建十个物体 挂上Mono脚本 MergeItem
for(int i = 0; i < 10; i++)
{
GameObject obj = new GameObject("TestMono"+i);
Tests.Add(obj.AddComponent<MergeItem>());
}
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
//第二步 随机销毁一个幸运儿 这里删除下标为5的 gameobj 注意要销毁的是gameobject不是脚本
DestroyImmediate((Tests[5] as MergeItem).gameObject);
}
if (Input.GetKeyDown(KeyCode.Alpha3))
{
//第三步 依次打印出来看看输出啥
for(int i = 0; i < Tests.Count; i++)
{
IMessage mergeItem = Tests[i];
if(mergeItem!=null)
Debug.LogError(Tests[i] + " ");
}
}
}
改成了在列表里不存类,和项目里一样存接口,实验结果:
这回不一样了,打印了十条结果,在第五条打印出了”null“,也就是说他绕过了 if(XXX!=null) 的判定,但是打印出了null。原来是接口的原因! 就是如果你存储了接口的索引,但是你继承这个接口的实体被销毁的时候,你这个接口的索引还在,不会为空,是变成带双引号的null,打印出来自然也是null。
也就是说, if(XXX!=null) 这种判空 对于接口 不是那么可靠
如果之后我继续调用这个接口的函数,就会报空。
搞了这么多年unity这种事情还是才知道,还是才疏学浅,惭愧惭愧呀~