2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Unity] UnityでSystem.WeakReferenceを使う

Last updated at Posted at 2017-04-10

概要

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

System.WeakReferenceは.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オブジェクトを動的生成
  2. 動的生成したオブジェクトをキャッシュへ登録 (WeakReferenceで参照される)
  3. シーン1のボタンでシーン2へ遷移
  4. シーン2がロードされキャッシュをクリーンする

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

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へ遷移する。

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);
    }
}

空実装。

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

シーン2へ遷移後
image

2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?