昨日の続きの記事になります。
前回のおさらい
-
UnityEngine.Object
では等値演算子==
(及び非等値演算子!=
)がオーバーロードされている - オーバーロードされた等値演算子では
Object.CompareBaseObjects
メソッドに判定処理を委譲している
public static bool operator ==(Object x, Object y) => Object.CompareBaseObjects(x, y);
public static bool operator !=(Object x, Object y) => !Object.CompareBaseObjects(x, y);
-
Object.CompareBaseObjects
メソッド内では純粋なnull
チェックやInstanceID
の等値判定に加えて、渡されたオブジェクトが「破棄」されていないか(Unityフレームワーク上で生存しているか)をチェックするObject.IsNativeObjectAlive
メソッドが実行されている
private static bool CompareBaseObjects(Object lhs, Object rhs)
{
// objectにアップキャストしてnullか判定
bool flag1 = (object)lhs == null;
bool flag2 = (object)rhs == null;
// 第1引数と第2引数がどちらもnullなら等価と判定
if (flag2 & flag1)
return true;
// Object.IsNativeObjectAlive: ObjectがUnityフレームワーク上で存在するものとして扱われているかを判定するメソッド(推定)
// 具体的にはDestroyされているかなどを返すと考えられる(他にもチェック項目がある可能性あり)
// 第1引数のみnullの時、第2引数がDestroyされているならtrue、されていないならfalse
if (flag1) return !Object.IsNativeObjectAlive(rhs);
// 第2引数のみnullの時、第1引数がDestroyされているならtrue、されていないならfalse
if (flag2) return !Object.IsNativeObjectAlive(lhs);
// 両方nullでなくかつDestroyされていないなら、IDが一致するかどうかで等価を判定
return lhs.m_InstanceID == rhs.m_InstanceID;
}
- 逆に言うと、
Object.IsNativeObjectAlive
メソッドがfalse
を返すようにすることがオブジェクトを「破棄(Destroy)」するということであると考えられる
なお、この記事では前回に引き続いてDestroyに破棄という訳を当てていますが、非公式なものです。
Destroyの挙動検証
前回のDestroyによる破棄というセクションでは
IsNativeObjectAliveメソッドの機能を書き下し版メソッドの中で「ObjectがUnityフレームワーク上で存在するものとして扱われているかを判定するメソッド(推定)」と記載しましたが、IsNativeObjectAliveメソッドがfalseを返す場合に、そのオブジェクトはUnityフレームワーク上で「破棄」されていると考えられます。
逆に言うと、Destroyメソッドなどが行う「破棄」の処理は、このメソッドがfalseを返すようにするということだと考えられます。
のようにメソッドの実装に基づく推論で話を勧めましたが、実際にDestroy前後でどのように値が変化しているかを検証してみます。
以下のようなテストコードを書きました。
using System;
using System.Collections;
using System.Reflection;
using UnityEngine;
using Object = UnityEngine.Object;
public class SampleTest : MonoBehaviour
{
private const string IsNativeObjectAlive = "IsNativeObjectAlive";
private const string DoesObjectWithInstanceIDExist = "DoesObjectWithInstanceIDExist";
private const string GetCachedPtr = "GetCachedPtr";
public GameObject FooGameObject;
public MonoBehaviour BarMonoBehaviour;
public ScriptableObject BazScriptableObjectPrefab;
public Material QuxMaterialPrefab;
public Texture QuuxTexturePrefab;
private ScriptableObject bazScriptableObject;
private Material quxMaterial;
private Texture quuxTexture;
private IEnumerator Start()
{
bazScriptableObject = Instantiate(BazScriptableObjectPrefab);
quxMaterial = Instantiate(QuxMaterialPrefab);
quuxTexture = Instantiate(QuuxTexturePrefab);
Debug.Log("Destroy前:");
InvokeAllObjects();
// GameObjectを破棄
Destroy(FooGameObject);
// MonoBehaviourを破棄
Destroy(BarMonoBehaviour);
// ScriptableObjectを破棄
Destroy(bazScriptableObject);
// Materialを破棄
Destroy(quxMaterial);
// Textureを破棄
Destroy(quuxTexture);
Debug.Log("Destroy直後:");
InvokeAllObjects();
// 1フレーム待機
yield return null;
Debug.Log("Destroy後1フレーム待機:");
InvokeAllObjects();
}
private void InvokeAllObjects()
{
InvokeAllMethods(FooGameObject, nameof(FooGameObject));
InvokeAllMethods(BarMonoBehaviour, nameof(BarMonoBehaviour));
InvokeAllMethods(bazScriptableObject, nameof(bazScriptableObject));
InvokeAllMethods(quxMaterial, nameof(quxMaterial));
InvokeAllMethods(quuxTexture, nameof(quuxTexture));
}
private static void InvokeAllMethods(Object obj, string name)
{
var isNativeObjectAlive = InvokeIsNativeObjectAlive(obj);
Debug.Log(
$"<color={GetColor(isNativeObjectAlive)}>{name} {IsNativeObjectAlive}: {isNativeObjectAlive}</color>");
var doesObjectWithInstanceIDExist = InvokeDoesObjectWithInstanceIDExist(obj.GetInstanceID());
Debug.Log(
$"<color={GetColor(doesObjectWithInstanceIDExist)}>{name} {DoesObjectWithInstanceIDExist}: {doesObjectWithInstanceIDExist}</color>");
var cachedPtr = InvokeGetCachedPtr(obj);
Debug.Log(
$"<color={GetColor(cachedPtr != IntPtr.Zero)}>{name} {GetCachedPtr}: {cachedPtr}</color>");
}
private static string GetColor(bool value)
{
return value ? "green" : "red";
}
private static bool InvokeIsNativeObjectAlive(Object obj)
{
return (bool)typeof(Object)
.InvokeMember
(
IsNativeObjectAlive,
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
null,
null,
new object[] { obj }
);
}
private static bool InvokeDoesObjectWithInstanceIDExist(int instanceID)
{
return (bool)typeof(Object)
.InvokeMember
(
DoesObjectWithInstanceIDExist,
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
null,
null,
new object[] { instanceID }
);
}
private static IntPtr InvokeGetCachedPtr(Object obj)
{
var method = typeof(Object)
.GetMethod
(
GetCachedPtr,
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new Type[0],
null
);
return (IntPtr)method.Invoke(obj, null);
}
}
これはUnityEngine.Object
クラスを継承している
GameObject
MonoBehaviour
ScriptableObject
Material
Texture
の代表的な5つのクラスのインスタンスに対してDestroy
を実行し、実行前と実行後で
IsNativeObjectAlive
DoesObjectWithInstanceIDExist
-
GetCachedPtr
の3つのメソッドの戻り値どうなるかを検証するテストです。
このテストを実行すると以下のように出力されました。
よって、この5つのパターンにおいてはDestroy
されることで
IsNativeObjectAlive
DoesObjectWithInstanceIDExist
はfalse
を返すようになり、GetCachedPtr
は0(NULL
)を返すようになるということがわかりました。
まとめ
上記の検証により、メソッドの戻り値が破棄前後で以下のように変化することがわかりました。
IsNativeObjectAlive | DoesObjectWithInstanceIDExist | GetCachedPtr | |
---|---|---|---|
破棄前 | true | true | インスタンス固有の値 |
破棄後 | false | false | 0 |
他にもDestroy
は
- アタッチされているコンポーネントに破棄された旨を通知
- 子オブジェクトに破棄を伝播
などのような処理をしている可能性もありえますが、externなメソッドで実装を見ることは難しいため未検証です。