Unity

[Unity] 유니티 오브젝트 Fake Null과 Null 처리

usingsystem 2023. 7. 13. 16:12
728x90

Fake Null

우리는 유니티 게임 오브젝트를 파괴하기 위해서 UnityEngine.Object를 GameObject.Destroy();를 통해 오브젝트가 메모리를 해제한다.

 

UnityEngine.Object클래스는 C++ 유니티 엔진 코드에 존재하는 NativeObject객체에 포인터를 가지고 있는 C++객체를 감싸 포함하고 있는 C#클래스이다. C++은 메모리를 포인터로 관리하고 C#은 가비지컬렉션이 메모리해제를 관리하며 이 차이점 때문에 Fake Null이 발생하게 된다. 즉 C++로 만들어진 클래스를 C#으로 한 번 더 감싸고 있다고 생각하자.

 

결국 GameObject.Destroy();를 통해 오브젝트를 파괴 한다는 것 은 UnityEngine.Object의 내부의 C++ NativeObject의 메모리만 해제한다. C++을 감싼 C#클래스인 UnityEngine.Object는 C# 객체이기 때문에 가비지컬렉터가 메모리를 해제하기 전까지 메모리를 해제할 수 없다.

 

Destroy 호출시 C++ Object 메모리 바로 해제 UnityEngine.Object 메모리 해제 안됨 가비지가 나중에 일괄 처리되며 게임 오브젝트가 파괴되면 UnityEngine.Object가 참조했던 C++ 오브젝트는 해제되지만 UnityEngine.Object의 메모리는 해제되지 않는다.(C#에는 메모리가 남아있기 때문에 완전한 삭제라고 볼 수 없다.)

 

그래서 Fake Null 이란 GameObject.Destroy();하여 파괴된 UnityEngine.Object를 ==연산자(UnityEngine에서 오버로딩 하여 새롭게 정의한 연산자)를 통해 Null 체크를 한다면 True가 나오게 된다. 이 처럼  c++에서는 메모리가 해제되고 c#에는 메모리가 완전히 삭제되지 않은 오브젝트에 대해 True가 나오는 걸 Fake Null이라고 한다.

 

SerializeField와 Fake Nullg

Fake Null은 [SerializeField]로 선언되어 유니티 에디터에서 아직 한번도 할당되지 않은 경우에도 Fake Null로 처리된다.

    GameObject go;
    void Start()
    {
        Destroy(go);
        if (go == null)
            Debug.Log("UnityEngine.Object");
        if ((object)go == null)
            Debug.Log("System.Object");
    }

출력결과는 UnityEngine.Object으로 나오며 Fake Null인 상태이다.

(object)로 형변환 한 후 == 연산자를 사용하면 UnityEngine.Object에서 오버로딩된 == 연산자가 아닌 기 때문에 이땐 False로 출력된다.

?? 연산자와? 연산자 주의

오브젝트 Null 체크 주의 해야 할 점은?? 연산자나? 연산자는 UnityEngine.Object에서 오버로딩한 연산자가 아니기 때문에 Fake Null을 출력하지 않는다. 

public GameObject go1;
public GameObject go2;

Destroy(go1);

go1 ?? go2; // go1이 null이면 go2 리턴, 아니라면 go1 그대로 리턴

Null 체크 연산의 비용

if(go == null) 연산은 GetComponent 연산보다 비싸다.

UnityEngine.Object의 == 연산은 Fake Null처리를 위해 C++ NativeObject 객체 유무를 체크하고 MonoBehaviour, ScriptableObject 등 함수도 호출하기 때문이다. 가장 비싼 연산은 한쪽만 Null일 경우이다.

연산 비용 줄이기

1. bool 타입으로 암시적 형변환 하여 사용하기

if (gameObject)

2. Destroy 후 Null 값을 준 후 

오브젝트가 사용되지 않는 시점이라면 명시적으로 Null을 대입하여  System.Object로 형변환 후 사용

System.Object로 형변환은 네이티브 리소스 확인 비용이 들지 않기 때문에 UnityeEngine.Object의 오버로딩된 ==을 사용할 경우 보다 훨씬 빠르다. SerializeField의 null은 무조건 Fake Null로 들어오기 때문에 속도가 빠르다고 무조건 System.Object로 형변환 후 사용하는 것은 바람직하지 않다.

 

        Destroy(gameObject);
        gameObject = null;
  • ReferenceEquals(gameObject, null); 사용
       if( ReferenceEquals(gameObject, null))
        {
        }
  • (object)gameObject 사용
	 if((object)gameObject == null)
        {
        }
  • null 결합 연산자 ?? 사용
go1 ?? go2;
  • null 조건 연산자 ? 사용
gameObject = gameObject ? gameObject : gameObject2;
  • 섞어 사용
    • gameObject가 Fake Null이라면 True
    • Destory상태라면 ReferenceEquals에선 true가 나오고 bool 타입으로 암시적 형변환 한 gameObject는 True로 나옴
       if(ReferenceEquals(gameObject, null) && gameObject == false)
        {
        }

 

 

참조

https://ansohxxn.github.io/unitydocs/fakenull/

 

Unity C# > 유니티 오브젝트의 fake null

🚀 Destroy

ansohxxn.github.io

https://overworks.github.io/unity/2019/07/16/null-of-unity-object.html

 

유니티 오브젝트의 null 비교 시 유의사항

다음글: 유니티 오브젝트의 null 비교 시 유의사항 2

overworks.github.io

https://overworks.github.io/unity/2019/07/22/null-of-unity-object-part-2.html

 

유니티 오브젝트의 null 비교 시 유의사항 2

이전글: 유니티 오브젝트의 null 비교 시 유의사항

overworks.github.io

 

728x90

'Unity' 카테고리의 다른 글

[Unity] UI 마우스 Drag로 UI 이동 방법  (0) 2023.10.27
[Unity] Unity 프로빌더 Probuilder  (0) 2023.10.11
[Unity] 미니맵 만드는 방법  (0) 2023.07.04
[Unity] 유용한 Asset  (0) 2023.07.03
[Unity] GUI 사용 예제  (0) 2023.05.18