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/
https://overworks.github.io/unity/2019/07/16/null-of-unity-object.html
https://overworks.github.io/unity/2019/07/22/null-of-unity-object-part-2.html
'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 |