Unity C# > 유니티 오브젝트의 fake null
카테고리: UnityDocs
태그: Unity Game Engine
🚀 Destroy
- 오브젝트가 Destroy 될 때
- 오브젝트가 메모리 해제되면 유니티의
Object
의==
연산자 오버로딩으로 인하여 해제된 객체를== null
비교 연산할 땐 그 객체를 참조하는 참조 변수들은 댕글링 포인터가 되는 것이 아닌 “null”이 되도록 처리된다. - 해제된 오브젝트의 컴포넌트를 참조하는 것들을 잘 확인해야 한다. 크러쉬가 날테니까! 오브젝트가 해제되면 당연히 그 오브젝트의 컴포넌트들에도 참조할 수 없다.
- 오브젝트가 메모리 해제되면 유니티의
🚀 fake null
✈ UnityEngine.Object의 구성
모든 게임 오브젝트들이 상속 받는
UnityEngine.Object
클래스
UnityEngine.Object
클래스는 C++ 유니티 엔진 코드에서 생성한 C++ 객체인 nativeObject 객체에 대한 포인터를 지니고 있는, 이를 감싸 Wrapping한 C# 클래스이다.
- UnityEngine.Object C# 클래스
- Pointer 👉 C++ 유니티 엔진 코드에서 생성한 C++ 객체인 nativeObject 객체의 주소를 담음
유니티에서 UnityEngine.Object
의 static 멤버 함수인 Destroy 함수는 유니티 게임 오브젝트를 파괴하는 함수이다. 이렇게 되면 사실은 게임 오브젝트(UnityEngine.Object
인스턴스) 내부의 C++ nativeObject 만 메모리 해제된다. 이를 감싸는 C# 클래스인 게임 오브젝트(`UnityEngine.Object` 인스턴스)는 C# 객체이기 때문에 해제될 수 없다. ✨C# 객체를 해제할 수 있는건 오직 가비지 콜렉터 뿐이기 때문이다.✨ (유니티 엔진 자체는 C++ 코드로 구현되어 있는듯 하다.)
- 유니티 오브젝트 Destroy 호출시
- Native C++ Object 👉 메모리 해제. 바로 삭제 됨.
- UnityEngine.Object 👉 메모리 해제 되지 않음. 나중에 가비지 콜렉터가 알아서 해제 함.
- 유니티 C++ 엔진 코드에서 Pointer 는 null 로 만든다. Native C++ Object이 해제 되면 포인터는 null로 초기화시킴
게임 오브젝트를 파괴하고나면 그 실체인, UnityEngine.Object
가 참조했던 C++ 오브젝트는 삭제되지만 C# 오브젝트인 UnityEngine.Object
은 남아 있다.
즉 알맹이(C++ NativeObject)만 사라지고 껍데기(UnityEngine.Object)는 가비지 콜렉터가 처리해줄 때까지 계속 메모리 상에 존재하는 것이다.
✈ UnityEngine.Object의 == 연산자 오버로딩
Destroy 했던 게임 오브젝트를 대상으로
==
연산을 하면 “null” 이 리턴된다. 👉 fake null
Destroy 후 가비지 콜렉터의 삭제를 기다리며 남아있는 UnityEngine.Object
는 ==
연산시 ==
연산자 오버로딩을 호출한다. UnityEngine.Object C# 인스턴스는 삭제되지 않았기 때문에 이렇게 피연산자가 되는 것이 가능하다는 것을 알 수 있다.
(알맹이인 C++ 객체가 있어야 하는 기타 메소드 호출 멤버 변수 호출 등등은 불가능하지만 껍데기의 연산자 오버로딩은 호출 가능한 것으로 보인다.)
namespace UnityEngine
{
public class Object
{
public static bool operator ==(Object x, Object y);
public static bool operator !=(Object x, Object y);
public static implicit operator bool(Object exists);
이렇게 UnityEngine.Object
은 ==
와 !=
그리고 UnityEngine.Object
가 묵시적으로 bool
로 형변환 되려고 할 때 호출되는 implicit operator도 오버로딩이 되어 있다.
==
,!=
연산자 오버로딩- 두
UnityEngine.Object
(유니티 게임 오브젝트)가 같은 지를 비교하는데, Destroy 된 게임 오브젝트도 비교가 가능하다.- Destroy 된 게임 오브젝트의 UnityEngine.Object C# 인스턴스 부분은 삭제되지 않았기 때문이다.(가비지 콜렉터가 해야 됨)
- 👉Destroy 된 오브젝트는 사실 실제로 삭제된건 C++ Native Object 이고 UnityEngine.Object C# 클래스 부분은 아직도 메모리 상에서 없어지지 않아 실제로 null이 된 것은 아니지만 없어진 것 처럼 행동해야 하기 때문에 “null”로 처리된다. 이게 바로 ✨fake null✨ 이다.
- 두
bool
묵시적 형변환 가능- 그래서 if (gameObject) 같은 표현이 가능하다. 유니티 게임 오브젝트는
bool
로 자동 형변환 된다. 이 오버로딩 때문에 가능한 일!- 👉 Destroy 된 오브젝트는 false 로 리턴되도록 오버로딩 구현이 되어 있다.
- 그래서 if (gameObject) 같은 표현이 가능하다. 유니티 게임 오브젝트는
아직 _stat
(private)에 컴포넌트가 할당 되본적도 없는 상태일 땐 이렇게 진짜 null
인 상태이다.
단! public
혹은 [SerializeField]
로 선언이 되어 유니티 에디터에서 할당을 받아야하는데 아직 한번도 되지 않은 경우는 null
이 아닌 fake null
로 처리 됨.
_lockTarget
을 Destory 시킨 후 살펴보니 "null"
로 되어 있다. 그냥 null
이 아닌 "null"
은 fake null이다. _lockTarget
가 포인터로 참조했던 C++ 객체 알맹이는 날라갔지만 _lockTarget
인스턴스의 UnityEngine.Object 껍데기는 남아있기 때문에 사실은 null이 아닌 것이다. 그러나 알맹이가 사라졌으므로 진짜 null은 아니지만 이를 null로 처리되게끔 _lockTarget
가 fake null인 “null” 상태라고 포장해주는 것이다. 이에 대한 처리가 UnityEngine.Object의 ==
연산자 오버로딩에 구현이 되어 있다. 따라서 진짜 null 이 된게 아님에도 Destroy된 오브젝트 == null 하면 True 로 리턴이 되게끔!
public GameObject go;
Destroy(go);
if (go == null)
Debug.Log("as UnityEngine.Object");
if ((object)go == null)
Debug.Log("as System.Object");
💎출력💎
as UnityEngine.Object
object
는System.Object
의 줄임말이다.- C#에 존재하는 모든 클래스는 이
System.Object
을 상속 받는다.UnityEngine.Object
도 마찬가지다.- 따라서
UnityEngine.Object
를object
로 업캐스팅 할 수 있다.
- go == null
- go 오브젝트는 사실 시스템상으론 null 이 아니지만
== null
에서 True로 처리되기 위해, 댕글링 포인터를 막기 위해 fake null 이 되어 있는 상태다. UnityEngine.Object
의==
연산자 오버로딩에 이 같은 내용이 반영이 되어 있다.- 따라서 이건
==
연산자 오버로딩에 의해 True 로 처리 된다.
- go 오브젝트는 사실 시스템상으론 null 이 아니지만
- (object)go == null
- go 오브젝트를
System.Object
로 형변환했으니==
은 더 이상UnityEngine.Object
의 오버로딩한==
가 아닌 그냥 일반적인==
가 된다. - 실제로 go 오브젝트 (
UnityEngine.Object
인스턴스)는 null이 된 것이 아니였으므로 이땐 false로 처리된다.UnityEngine.Object
의==
연산자가 호출되지 않기 때문에
- go 오브젝트를
✈ 오브젝트 null 체크의 단점
null 연산 사용시 의도치 않은 결과 발생
1진짜 null
이 되는 것이 아니라 fake null 이기 때문에 혹시라도 추후 C#의 ??
, ?
같은 null 판별 연산을 할 때 원치 않는 결과가 나올 수 있다. 진짜 null이 아니기 때문이다. (??
,?
같은 null 연산은 유니티 게임 오브젝트에겐 적용하지 않는게 좋겠다.)
public GameObject go1;
public GameObject go2;
Destroy(go1);
go1 ?? go2; // go1이 null이면 go2 리턴, 아니라면 go1 그대로 리턴
go2
가 리턴되는 일은 없다. go1
은 Destroy 된 후 null은 아니지만(C# UnityEngine.Object 아직 해제 안됨) 댕글링 포인터가 되지 않도록, ==
, bool 형변환
등등에서 null로 처리되도록 하기 위하여 fake null 이 된 상태다. 따라서 진짜 null
인건 아니기 때문에 go1
은 파괴 되었음에도 불구하고 go2
로 리턴되지 않는다.
오브젝트의 null 체크 연산은 매우 비싸다.
if (component == null) or if (go == null) 이런 오브젝트, 컴포넌트 비교 연산은 심지어 GetComponent 보다 비싸다.
UnityEngine.Object 의 == 연산자는 유니티 오브젝트와 진짜 null
을 비교할 때는 (go == null), 즉 한쪽만 null일 때는 null이 아닌 그 한쪽 오브젝트의 네이티브 C++ 객체의 유무 또한 체크하고 MonoBehaviour, ScriptableObject 등으로 캐스팅도 하는 등등 함수도 2 번 호출하고.. 아무튼 비용이 많이 든다. 유니티 오브젝트가 Destroy 되어 C++ 알맹이가 없는 상태면 null
과 같다고 fake 처리 해주기 위해서다.
위 블로그 링크에서 UnityEngine.Object의 implicit bool
과 ==
연산자 코드를 확인하자!! 둘 다 한 쪽만 null인 경우엔 비용이 비싸지는 것을 확인할 수 있다. 차라리 둘 다 null이 아니거나 둘 다 null이면 별도의 캐스팅, 함수 호출 없이 금방 빠져 나오는 것을 확인할 수 있음.
✈ 오브젝트 null 체크를 대신 할 추천 방법
- 1️⃣ 오브젝트를 bool로 묵시적 형변환
- 느리지만 안정적.
UnityEngine.Object
는 implicit bool 로 암시적 형변환 연산자 오버로딩 또한 되어 있다. Destroy 된 오브젝트면 false 로 형변환되게끔!- 여전히 fake null 인 상태인 것에선
==
과 전혀 다름이 없긴 하지만 좀 더 안정적if (gameObject) ///...
- 2️⃣ Destroy 후 진짜 null 대입
- 안정적. *(근데 굳이 코드 한 줄을 더..? C++ 같잖아!)
Destroy(go); go = null;
- 안정적. *(근데 굳이 코드 한 줄을 더..? C++ 같잖아!)
- 3️⃣
System.Object
로 형변환하여 비교 (혹은 obejct.ReferenceEquals 함수 사용)- 훨씬 빠름. fake null 상태라도 null 과 같다고 표현하려고 노력 하는..
UnityEngine.Object
의 연산자 오버로딩을 거치지 않기 때문! - 그냥
UnityEngine.Object
로서의 있는 그대로 모습을 빠르게 비교UnityEngine.Object obj; // 👉 obj 가 진짜 null이라면 True ReferenceEquals(obj, null); if ((object)obj == null) // 👉 obj가 falke null 이라면 True. // Destroy 후 obj 는 fake null 상태가 됨. (진짜 null 상태가 아님) // (object)obj == null 의 결과는 False. obj 는 진짜 null이 아니므로. // obj 또한 그 자체로 null이 사실 아니므로 obj 하나로는 True인 상태 // false && true 의 결과는 false. !ReferenceEquals(obj, null) && obj; if (((object)obj == null && obj) == false)
- 훨씬 빠름. fake null 상태라도 null 과 같다고 표현하려고 노력 하는..
reference
- 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
- https://jacx.net/2015/11/20/dont-use-equals-null-on-unity-objects.html
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기