반응형

 

 

"Fake Null"

 

 

개발을 하다보면 Destroy()로 오브젝트를 지웠는데도,
변수는 “null이 아닌 것처럼” 보이거나 ?.를 썼는데도 MissingReferenceException이 터지는 순간이 있다.

 


특히 Unity에서는 C# 객체(관리 객체)와 엔진 내부 네이티브 객체의 생명주기가 다르기 때문에,
겉보기엔 null인데 실제 참조는 남아있는 Fake Null 상태가 생길 수 있다.

 

따라서, 오늘은 Fake Null이란 무엇이고, 왜 발생하는지 간단하게 정리해 볼 예정이다.

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

1) Fake Null이란?

 


Unity에서 GameObject, Component, ScriptableObject처럼 
UnityEngine.Object를 상속받는 타입은

실제로는 C# 참조가 남아있더라도 엔진 내부 네이티브 객체가 파괴되면

obj == null 비교에서 null처럼 동작할 수 있다.

즉,
C# 관점의 진짜 null

Unity 생명주기 관점의 null이 달라서 생기는 현상이다.

 

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

2) 왜 생기나? (관리 객체 vs 네이티브 객체 생명주기)


Unity 문서에서도 UnityEngine.Object 인스턴스는 네이티브 counterpart와 연결되어 있고,
네이티브 쪽이 먼저 파괴되면 관리 객체는 GC 전까지 남아 detached 상태가 될 수 있다고 설명한다.

그리고 Unity는 이런 파괴된 오브젝트 참조를 잡기 위해 == 연산자(그리고 if(obj) 같은 implicit bool)를 커스텀해서,
파괴된 객체도 null처럼 비교되도록 만들어둔다.

추가로 Destroy(obj)는 즉시 사라지는 게 아니라 현재 Update 루프 이후에 실제 파괴가 일어난다.
그래서 프레임 경계를 지나면서 Fake Null 상황을 더 자주 만나게 된다.

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

3) 예시 코드

 

using UnityEngine;
using System;

public class FakeNullEx : MonoBehaviour
{
    private IEnumerator Start()
    {
        var go = new GameObject("Demo");

        Debug.Log($"[생성 직후] go == null : {go == null}");
        Debug.Log($"[생성 직후] ReferenceEquals(go, null) : {ReferenceEquals(go, null)}");

        Destroy(go);

        // Destroy는 프레임 끝에서 처리되므로 다음 프레임까지 기다림
        yield return null;

        Debug.Log($"[Destroy 후] go == null : {go == null}"); // 보통 true
        Debug.Log($"[Destroy 후] ReferenceEquals(go, null) : {ReferenceEquals(go, null)}"); // false (진짜 null 아님)
        Debug.Log($"[Destroy 후] (object)go == null : {(object)go == null}"); // false
    }
}

 

 

 

 

go == null → true 

Unity 기준으로 이미 Destroy된 오브젝트

 


ReferenceEquals(go, null) → false 

C# 참조 자체는 아직 null이 아님(래퍼가 남아있음)

 

 

(object)go == null → false

(object)로 캐스팅하면 UnityEngine.Object의 오버로드된 ==가 아니라
System.Object 레벨의 참조 비교로 가게 되는 경우가 많다.

(오버로드 우회 목적)

결론적으로 ReferenceEquals와 같은 의미로 동작해서 false가 나오게 된다.

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

 

5) Unity에서 자주 발생하는 요인 3가지



코루틴/async에서 대기 후 접근
yield return ... / await ... 사이에 다른 로직이 오브젝트를 Destroy하면, 

이후 접근 시 예외가 터질 수 있다.

 


?./??로 '안전 접근'했다고 믿는 경우
Unity Object에는 기대한 “안전 장치”가 아닐 수 있다.

(생명주기 체크 우회)

 


이벤트/콜백 구독 해제 누락
파괴된 객체가 계속 콜백을 받으면, 

콜백 내부에서 참조 접근으로 예외/오동작이 발생할 수 있다.

 

 

 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

 

6) 오늘의 한 줄 요약

 

 

Fake Null은 Destroy로 네이티브(엔진) 객체가 파괴됐지만 C# 참조는 남아 obj == null이 true처럼 동작하는 상태이다.

반응형

+ Recent posts