记MonoBehavior与单例模式碰撞的火花

这几天写代码遇到一个很奇怪的bug(有不奇怪的bug吗),简单的说就是某个事件会重复触发。经过层层debug、把原来的代码精简精简再精简,终于发现了问题所在~现在把这个问题简化记录一下。

要做的事情:

启动一个定时器,每隔100ms调用一下Behaviour类的Foo方法。Behavior是采用单例模式的类。

代码如下(unity平台下):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class YYY: MonoBehaviour {
    void Start () {
       TimerClass.TimerControl();
    }
}

class Behavior : MonoBehaviour
{
    static Behavior behavior = null;
    public static Behavior Instance
    {
        get
        {
            if (behavior == null)
            {
                Debug.Log("instance is null and will be created");
                behavior = new Behavior();

            }
            return behavior;
        }
    }

    public void Foo()
    {
        Debug.Log("Foo completed");
    }

}

class TimerClass : MonoBehaviour
{
    static System.Timers.Timer timer = new System.Timers.Timer();
    public static void TimerControl()
    {
        timer.Interval = 100;
        timer.AutoReset = true;
        timer.Elapsed += new System.Timers.ElapsedEventHandler(TimerElapse);

        timer.Start();
        System.Threading.Thread.Sleep(500);
        timer.Stop();
    }

    public static void TimerElapse(object source, System.Timers.ElapsedEventArgs args)
    {
        Behavior.Instance.Foo();
    }
}
输出:

这里写图片描述

哎呦我去,这不是单例模式,怎么会每次用都会创建一次?
我最先怀疑的是System.Timers.Timer的问题,通过查资料知道System.Timers.Timer采用了线程池,每过一个Interval就会从线程池里取一个线程执行Elapsed。会不会是每次线程回收后Behavior的实例就被销毁了,然后新取得线程需要重新实例化?很有可能!而且这个时候有人给我发了这个:
sss
于是乎我开开心心地去VS上准备写一个简化的demo来记录一下这个问题,结果发现同样的代码结果竟然变了!!!
把unity的代码简单修改后(去除UnityEngine的引用、类不再继承MonoBehavior、Debug改成Console.WriteLine、Start改成Main),发现确确实实只实例化了一次!

什么鬼~

当时我的内心是有点崩溃的。因为定位到这个地方已经花了很多时间了,结果发现问题好像并不在这里。。。

两份可以说是一模一样的代码,竟然结果千差万别~emmm,我把它归结为“平台差异造成的结果不同”:看起来unity的运行机制和单纯的C#不一样呀~

于是我打算写博客记录一下这个伟大的发现~由于之前的电脑不能联网,我用自己的电脑写,emmm,也换了unity Editor。就在我把之前的代码敲完运行时,一条warning让我虎躯一震:
zhen
之前看的某本书隐隐约约让我想起:继承MonoBehavior的类及MonoBehavior本身都不可以使用new实例化!

难道是因为这个behavior = new Behavior();一直都没有执行成功,然后behavior一直是null??

我在new语句之后检查了一下behavior,好吧,确实是null~

真相大白!

至于为什么返回null之后还是能成功调用Foo我还是一脸懵逼,我需要缓一下再来明白这个bug。
为什么返回null还是能成功调用Foo请看MonoBehavior子类返回null仍然可以使用

总结一下,这个bug出现的原因在于继承了MonoBehavior,导致单例模式失败。其实这个类是没有必要继承MonoBehavior的。具体要不要继承请看下面的内容:

要不要继承MonoBehaviour

继承的意义就是可以使用父类的一些“遗产”(属性、方法等等),那我们就需要先了解一下MonoBehavior
可以看到,MonoBehavior有runInEditMode、useGUILayout属性,Invoke、StartCoroutine等方法,还有一些用来相应时间的Messages。所以你要使用以上方法的话就需要继承MonoBehavior,否则就不需要继承。

其实,MonoBehavior包含的属性、方法等都是与GameObject相关。所以,一般来说,需要挂在GameObject上的组件类,是需要继承MonoBehavior的。否则,就不需要继承,就算继承了也没有什么意义。

如果你的类无需引擎提供的各种初始化, 更新及析构, 物理, 渲染等的回调. 最好不要继承MonoBehavior继承后, 引擎会在事件触发时, 通过反射调用各种函数. 这是需要消耗性能的.当然, 如果你的类压根没挂上GameObject. 理论上说, 继承了也没啥卵。
如果你要挂在GameObject上不继承怎么挂?如果只是自己定义的一个数据类,继承了mono,你就不能通过new来实例化一个对象,mb内部的方法也不能调用,木有任何意义。需要挂gameobject的组件类继承,其他类不继承

如果真的需要继承MonoBehavior
void Awake () 
{
    instance = this;
}

总结

1. 当你遇到bug不妨试试最新的IDE
2. 不要认为成熟的写法就没有问题。在看到那条warning前我从没怀疑过是单例模式出了问题,因为几乎所有的教程会这样写。然后问题就恰恰处在这里。emmm,你越是相信的,越是危险啊~
3. 要有刨根问底的精神。如果不是warning,我就停在了“平台差异会造成结果不同”的结论上了。倘若我在多问一下平台有什么差异,我就会发现Mono以及MonoBehavior了。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值