Appearance
订阅和取消订阅
一句话理解
订阅就是:
事件 += 我的函数;取消订阅就是:
事件 -= 我的函数;你可以把事件理解成一张“方法清单”。 += 是把方法加入清单。 -= 是把方法从清单移除。 事件触发时,清单里的方法会依次执行。
最典型的 Unity 写法
发布者:
c
using System;
using UnityEngine;
public class PlayerHealth : MonoBehaviour
{
public event Action<int> OnHpChanged;
public void TakeDamage(int damage)
{
currentHp -= damage;
OnHpChanged?.Invoke(currentHp);
}
private int currentHp = 100;
}订阅者:
c
using UnityEngine;
public class HealthUI : MonoBehaviour
{
public PlayerHealth player;
private void OnEnable()
{
player.OnHpChanged += Refresh;
}
private void OnDisable()
{
player.OnHpChanged -= Refresh;
}
private void Refresh(int hp)
{
Debug.Log("刷新血量:" + hp);
}
}这里要分清 3 个角色:
PlayerHealth 是事件发布者。 HealthUI 是事件订阅者。 Refresh 是被登记进去的回调函数。
为什么要取消订阅?
因为事件会保存对方法的引用。 如果 HealthUI 已经关闭或销毁,但还挂在 PlayerHealth.OnHpChanged 里,后面玩家扣血时,它仍然可能被调用。
结果可能是:
UI 重复刷新。 音效播放多次。 对象销毁后还被访问。 旧对象无法及时释放。 出现空引用异常。
Unity 里常用配对
启用期间才监听:
c
OnEnable 订阅
OnDisable 取消订阅整个对象生命周期都监听:
c
Awake 或 Start 订阅
OnDestroy 取消订阅UI 按钮:
c
button.onClick.AddListener(OnClick);
button.onClick.RemoveListener(OnClick);新手最容易踩的坑
第一,重复订阅。
c
player.OnDead += ShowGameOver;
player.OnDead += ShowGameOver;这样事件触发时,ShowGameOver 会执行两次。
第二,取消的不是同一个方法。
c
button.onClick.AddListener(() => Debug.Log("点击"));
button.onClick.RemoveListener(() => Debug.Log("点击"));这两段 lambda 看起来一样,但通常不是同一个回调实例,所以移除可能失败。需要移除时,优先用具名方法:
c
button.onClick.AddListener(OnClick);
button.onClick.RemoveListener(OnClick);
void OnClick()
{
Debug.Log("点击");
}第三,长寿命对象引用短寿命对象。
比如全局管理器、单例、静态事件很容易活得比普通场景对象久。 订阅这类事件时,取消订阅一定要非常明确。
参考链接:
Microsoft - EventsMicrosoft - DelegatesMicrosoft - event keywordUnity - UnityEvent.AddListenerUnity - UnityEvent.RemoveListenerUnity - MonoBehaviour.OnEnableUnity - MonoBehaviour.OnDisable