有名な話ではあると思いますが時々やらかすことあるので備忘録。
MonoBehaviourは==をオーバーライドしていて、
OnDestroy以降はnullを返す。
OnDestroyが呼ばれてもメモリから消えているわけではない。
まあC#なんだからそりゃそうなんだけれど、
この余計な親切のおかげでメモリリークしやすい。
例えば下記のように二つのMonoBehaviourがあったとして。
public class Sub : MonoBehaviour
{
void Update()
{
if (Input.GetMouseDown(0)) {
Destroy(this);
}
}
}
public class Main : MonoBehaviour
{
Sub ReferenceToSub;
void Start()
{
ReferenceToSub = gameObject.AddComponent<Sub>;
}
void Update()
{
if (ReferenceToSub == null) {
Debug.Log("SubはすでにDestroyされている。");
return;
}
}
}
マウスクリックすることでReferenceToSubはnullを返すようになる。
しかしMainが死ぬまでSubはメモリに残り続ける。
これを防ぐには例えば下記のように明示的にnullを入れてやる。
void Update()
{
if (ReferenceToSub == null) {
Debug.Log("SubはすでにDestroyされている。");
ReferenceToSub = null;
return;
}
}
あと、下記のようにインターフェースを使った場合。
public interface ITest
{
void Test();
}
public class Sub : MonoBehaviour, ITest
{
void Update()
{
if (Input.GetMouseDown(0)) {
Destroy(this);
}
}
public void Test()
{
}
}
public class Main : MonoBehaviour
{
ITest ReferenceToSub;
void Start()
{
ReferenceToSub = gameObject.AddComponent<Sub>;
}
void Update()
{
if (ReferenceToSub == null) {
Debug.Log("SubはすでにOnDestroyされている。");
return;
}
}
}
これはSub.OnDestroyが呼ばれた後も
ReferenceToSubはnullを返さない模様。
なので保持するインターフェースの実態が
MonoBehaviourである可能性があるなら、
そいつがOnDestroyされたことは別の手段で知る必要がある。
インターフェースはGC.Allocを回避しつつ
コールバック処理できるありがたい存在なんだけど、
こういう罠もある。
最後に、これも有名な話ですが
OnDestroyが呼ばれた後、
ReferenceToSub==nullはtrueなのに
ReferenceToSub?はnullを返さない。
これ何とかしてくれんかなあ。