[Unity] UnityでSystem.WeakReferenceを使う

#概要
UnityでSystem.WeakReferenceを使うテスト。

System.WeakReference<T>は.Net 4.5から。
https://msdn.microsoft.com/ja-jp/library/gg712738(v=vs.110).aspx

#環境
Unity5.5.2p4

#結果
使える。
System.WeakReferenceのTargetに参照が格納されていて型はSystem.Object。
このためUnityEngine.Objectの特殊なnullチェックが処理されない。
System.WeakReferenceのIsAlive, Targetの処理を変更すれば使用することができる。

#テストコード
大まかな流れは以下の通り。

1. シーン1のTestObject1オブジェクトでTestObject2オブジェクトを動的生成
1. 動的生成したオブジェクトをキャッシュへ登録 (WeakReferenceで参照される)
1. シーン1のボタンでシーン2へ遷移
1. シーン2がロードされキャッシュをクリーンする


WeakReferenceをテストするキャッシュクラス。

```csharp:MonoBehaviourCache.cs
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;

[DefaultExecutionOrder(int.MinValue)]
public class MonoBehaviourCache : MonoBehaviour
{
    class UnityWeakReference<T> : System.WeakReference where T : UnityEngine.Object
    {
        public UnityWeakReference(T target) : base(target) {}

        public override bool IsAlive
        {
            get
            {
                // MEMO: UnityEngine.Objectのnullチェックが特殊なためキャストして調査する
                UnityEngine.Object obj = Target as UnityEngine.Object;
                return obj != null;
            }
        }

        public new T Target
        {
            get { return base.Target as T; }
            set { base.Target = value; }
        }

        public bool TryGetTarget(out T target)
        {
            target = base.Target as T;
            return target != null;
        }
    }

    static MonoBehaviourCache _instance;

    Dictionary<int, UnityWeakReference<MonoBehaviour>> _dictionary = new Dictionary<int, UnityWeakReference<MonoBehaviour>>();

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void CreateInstance()
    {
        Debug.LogFormat("MonoBehaviourCache.CreateInstance()");
        GameObject gameObject = new GameObject("MonoBehaviourCache");
        _instance = gameObject.AddComponent<MonoBehaviourCache>();
        DontDestroyOnLoad(gameObject);

        SceneManager.sceneUnloaded += SceneUnloaded;
    }

    private static void SceneUnloaded(Scene scene)
    {
        Debug.LogFormat("MonoBehaviourCache.SceneUnloaded() scene:{0}", scene.name);

        List<int> removeIds = new List<int>(_instance._dictionary.Count);

        foreach(var pair in _instance._dictionary)
        {
            if (pair.Value.Target == null)
            {
                Debug.LogFormat("MonoBehaviourCache.SceneUnloaded() Unused object. InstanceID:{0}", pair.Key);
                removeIds.Add(pair.Key);
            }
            else
                Debug.LogFormat("MonoBehaviourCache.SceneUnloaded() Already used object. InstanceID:{0}, Type:{1}", pair.Key, pair.Value.Target.GetType());
        }

        foreach (int id in removeIds)
            _instance._dictionary.Remove(id);
    }

    static public bool Add(MonoBehaviour monoBehaviour)
    {
        if (monoBehaviour == null)
            return false;

        int id = monoBehaviour.GetInstanceID();

        if (_instance._dictionary.ContainsKey(id))
        {
            Debug.LogFormat("MonoBehaviourCache.Add() Already contains instance id of object. InstanceID:{0}", id);
            return false;
        }

        _instance._dictionary.Add(id, new UnityWeakReference<MonoBehaviour>(monoBehaviour));
        return true;
    }

    static public T Get<T>(int instanceId) where T : MonoBehaviour
    {
        UnityWeakReference<MonoBehaviour> weakReference;

        if (!_instance._dictionary.TryGetValue(instanceId, out weakReference))
        {
            Debug.LogWarningFormat("MonoBehaviourCache.Get() Not found element. instanceId:{0}", instanceId);
            return null;
        }

        return weakReference.Target as T;
    }
}
```

インスタンス生成時にTestObject2オブジェクトを生成する。
ボタンが押されたらシーン2へ遷移する。

```csharp:TestObject1.cs
using UnityEngine;
using UnityEngine.SceneManagement;

public class TestObject1 : MonoBehaviour
{
    int _instanceId;

    private void Awake()
    {
        GameObject gameObject = new GameObject("TestObject2");
        TestObject2 testObject2 = gameObject.AddComponent<TestObject2>();
        _instanceId = testObject2.GetInstanceID();
        Debug.LogFormat("TestObject1.Awake() testObject2 id:{0}", _instanceId);

        MonoBehaviourCache.Add(testObject2);
        testObject2 = MonoBehaviourCache.Get<TestObject2>(_instanceId);
        Debug.LogFormat("TestObject1.Awake() testObject2:{0}", testObject2);
    }

    public void OnPressedButton()
    {
        SceneManager.LoadScene("Scene2");
    }

    private void OnDestroy()
    {
        Debug.LogFormat("TestObject1.OnDestroy()");
        TestObject2 testObject2 = MonoBehaviourCache.Get<TestObject2>(_instanceId);
        Debug.LogFormat("TestObject1.OnDestroy() testObject2:{0}", testObject2);
    }
}
```

空実装。

```csharp:TestObject2.cs
using UnityEngine;

public class TestObject2 : MonoBehaviour
{
}
```

#出力ログ

```
MonoBehaviourCache.CreateInstance()
TestObject1.Awake() testObject2 id:-53294
TestObject1.Awake() testObject2:TestObject2 (My.TestObject2)
TestObject1.OnDestroy()
TestObject1.OnDestroy() testObject2:null
MonoBehaviourCache.SceneUnloaded() scene:Scene1
MonoBehaviourCache.SceneUnloaded() Unused object. InstanceID:-53294
```

#プロファイラでの調査

シーン1
![image](https://qiita-image-store.s3.amazonaws.com/0/36154/c9fd6abd-7228-5248-479b-b22ee86ffb91.png)

シーン2へ遷移後
![image](https://qiita-image-store.s3.amazonaws.com/0/36154/5239130d-deda-8483-dd7a-b162089ccd7f.png)