-
[Unity] 유니티 fake null과 관련한 이야기들쾌락없는 책임 (공부)/Unity 2023. 1. 16. 11:56반응형
개요
이전부터 문서들을 참고할 때 많이 보이는 내용이지만 이제 와서야 정리를 하게 된 내용입니다. 유니티가 오브젝트를 어떻게 가지고 있는지, GC, operator == 등과 관련한 이야기들을 확인할 수 있습니다.
Destroy()를 하면 어떤 일이 벌어지는가
일단 현상을 먼저 보고 설명을 드리겠습니다.
IEnumerator Start() { CapsuleCollider2D col = gameObject.AddComponent<CapsuleCollider2D>(); yield return null; Destroy(col); yield return null; IsUnityNull(col); IsSystemNull(col); } private void IsUnityNull(Object obj) { if (obj == null) Debug.Log("Unity is null"); else Debug.Log("Unity is not null"); } private void IsSystemNull(object obj) { if (obj == null) Debug.Log("System is null"); else Debug.Log("System is not null"); }
시작하자마자 컴포넌트를 하나 붙인 뒤 다음 프레임에서 바로 Destroy()를 해 줍니다. 그런 다음 이게 null인지 알아보기 위해서 두 함수에 값을 넣어본 결과 UnityEngine.Object는 null이라고 하고 System.object는 null 이 아니라고 하는 기이한 현상이 일어나게 됩니다.
자세한 구현 사항은 알 수 없으나 UnityEngine.Object의 경우 operator ==, != 들이 오버로딩 되어 있습니다. 이 때문에 == null 체크를 하는 게 System.object과는 다른 로직을 하게 됩니다. 위 코드를 보면 아래와 같은 로직을 따른 것이죠.
- 유니티는 내부적으로 C++ 코드가 있어 실제 객체는 C++로, 클래스 내에서 보이는건 C#으로 래핑 되어 있음
- Destroy()를 하면 C++의 실제 객체는 삭제
- C#으로 래핑된 부분은 아직 GC의 동작을 기다리고 있음
- UnityEngine.Object == 는 C# 래핑 부분뿐 아니라 실제 객체도 존재하는지에 대한 부분도 보고 있음
- 위 경우 실제 C++ 객체는 Destroy에 의해 삭제되었으니 이를 알고 null이라 해줌
- System.object의 경우 아직 C#의 래핑된 부분이 남아 있으므로 null이 아니라고 함
때문에 이로 인해서 == null 체크의 시간이 오래 걸리게 되는 것입니다. 이 부분으로 인해서 고민할 부분이 생기게 되는 것이죠.
추가적인 fake null이 있을까?
일단 알아본 바로는 Destroy 된 것들 외 아직 할당이 안된 인스펙터 변수들이 포함되었습니다.
public GameObject PuObject; [SerializeField] private GameObject SPObject; private GameObject PrObject; IEnumerator Start() { yield return null; Debug.Log($"public GameObject is null : {PuObject == null}, {(object)PuObject == null}"); Debug.Log($"serialized private GameObject is null : {SPObject == null}, {(object)SPObject == null}"); Debug.Log($"private GameObject is null : {PrObject == null}, {(object)PrObject == null}"); }
이렇게 인스펙터에 할당이 안된 변수들은 operator == 같은 연산을 해야 원하는 값을 얻을 수 있을 겁니다.
operator ==처럼 fake null을 검출하는 연산이 있는가?
operator == 이 어떤 과정을 거치는지, 왜 느린지를 알아봤으니 이를 개선해봐야 할 것 같습니다. 먼저 여러 연산들을 보면서 operator ==과 동일한 값을 내는 연산이 있는지 알아보겠습니다.
private void DefaultCompare(Object obj) { Debug.Log($"== : {(obj == null)}"); } private void SystemIsCompare(object obj) { Debug.Log($"System is : {(obj is null)}"); } private void UnityIsCompare(Object obj) { Debug.Log($"System is : {(obj is null)}"); } private void ReferenceCompare(object obj) { Debug.Log($"ReferenceEquals : {ReferenceEquals(obj, null)}"); } private void ObjectReferenceCompare(object obj) { Debug.Log($"Object.ReferenceEquals : {Object.ReferenceEquals(obj, null)}"); } private void JustCompare(Object obj) { if(obj) Debug.Log($"just Not null"); else Debug.Log($"just null"); }
위 코드를 통해 fake null 상태인 [SerializeField] private Gameobject 변수를 넣고 본 결과입니다. 일단 보시는 대로 ==와 바로 if 문의 조건에 넣은 걸 제외하고는 다 fake null인지 모르는 상태입니다. 즉, 유니티에서 준비한 operator ==과 암시적 bool 변환을 제외하고 fake null을 검증할 수 없습니다.
즉 이전에 'is' 연산이 유니티에서 빨랐던 이유는 is 연산의 경우 fake null을 알지 못하고 C#의 조건만 비교하기 때문입니다. 그래서 유니티에서 null 체크를 할 때는 가급적 operator ==과 암시적 bool 변환을 사용하는 게 좋습니다.
그럼 operator == 말고 사용할 수 있는 경우는?
만일 그렇다면 속도에 있어서 아쉬운 부분이 많을 것입니다. 그러면 operator == 대신 다른 연산을 사용할 수 있는 경우는 어떤 경우일까요?
- GameObject의 transform 같은 '당연히 있어야 하는 객체'들을 캐싱하는 경우
- UnityEngine.Object와 같이 fake null일 염려가 아닌 경우
- 싱글톤 객체와 같이 파괴가 예정되어 있지 않은 객체
- Destroy를 사용하지 않는 경우
- 이 경우 프로그래머에게 '명시되지 않은 규약'이 생기는 거라 비추천
- 규약을 더할 바에는 차라리 operator ==를 사용하는 게 좋을 것 같습니다.
뭐 이 정도가 될 것 같은데 더 추가되는 대로 포스트를 수정하겠습니다.
참고 자료
대부분의 내용을 가장 공신력 있는 유니티 공식 블로그를 참고했습니다. 왜 성능이 떨어지는 operator ==를 고수하는가에 대한 이야기로 한 번쯤 읽어볼 만한 것 같습니다.
반응형'쾌락없는 책임 (공부) > Unity' 카테고리의 다른 글
[Unity] 유니티 멀티플레이를 위한 Lobby, 플레이어 매치메이킹 (0) 2023.01.30 [Unity] GPGS Android Setup Invalid classname: Object reference not set to an instance of an object 오류 날 시 (0) 2023.01.30 [Unity] NewtonSoft 사용중 빌드시 json 파일이 보이지 않는다면 (0) 2022.10.25 [Unity/C#] C# 에서 리스트 셔플하기 - C# list shuffle (3) 2022.09.30 [Unity] 유니티 내 길찾기 알고리즘을 넣어보기 (0) 2022.07.24