UObject 에서 Outer 는 구조 를 유지 하는 기능 을 한다. 이번 포스팅에서는 대표적인 Outer 의 사용처를 먼저 살펴보고 GarbageCollection 에서 Outer 가 어떻게 작용되는지 살펴보자.
인터넷의 다른 Article 에도 잘 나와 있지만 일단 World 구조를 유지 한다.
class UWorld* UObject::GetWorld() const
{
if (UObject* Outer = GetOuter())
{
return Outer->GetWorld();
}
return nullptr;
}
UWorld* AActor::GetWorld() const
{
// CDO objects do not belong to a world
// If the actors outer is destroyed or unreachable we are shutting down and the world should be nullptr
if (!HasAnyFlags(RF_ClassDefaultObject) && ensureMsgf(GetOuter(), TEXT("Actor: %s has a null OuterPrivate in AActor::GetWorld()"), *GetFullName())
&& !GetOuter()->HasAnyFlags(RF_BeginDestroyed) && !GetOuter()->IsUnreachable())
{
if (ULevel* Level = GetLevel())
{
return Level->OwningWorld;
}
}
return nullptr;
}
또 Packaging 등에서 Serializing 의 구조 또한 유지 한다.
UCLASS()
class EKUE54_API UMyTestObj : public UObject
{
GENERATED_BODY()
public:
virtual void BeginDestroy() override;
};
void UMyTestObj::BeginDestroy()
{
UE_LOG(LogTemp, Warning, TEXT("UMyTestObj::BeginDestroy() - %s"), *GetName());
Super::BeginDestroy();
}
class AEkUe54GameMode : public AGameModeBase
{
GENERATED_BODY()
protected:
TWeakObjectPtr<class UMyTestObj> WeakTestObj;
public:
AEkUe54GameMode();
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
virtual void BeginDestroy() override;
};
void AEkUe54GameMode::BeginPlay()
{
Super::BeginPlay();
{
UMyTestObj* MyTestObj = NewObject<UMyTestObj>(this);
WeakTestObj = MyTestObj;
GEngine->ForceGarbageCollection(true);
}
}
void AEkUe54GameMode::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
UE_LOG(LogTemp, Warning, TEXT("WeakTestObj is - %s"), WeakTestObj.IsValid() ? TEXT("Valid") : TEXT("Invalid") );
}
void AEkUe54GameMode::BeginDestroy()
{
UE_LOG(LogTemp, Warning, TEXT("AEkUe54GameMode::BeginDestroy() - %s"), *GetName());
Super::BeginDestroy();
}
UMyTestObj 를 생성하고 Outer 로 AEkUe54GAmeMode 를 지정 하였다. GameMode 는 해당 World 가 내려가기 전에는 파괴되지 않는다. 추적을 위해서 WeakTestObj 에 Referencing 하였다. 따라서 생성된 MyTestObj 로의 GC Referencing 은 없다.
[2024.09.29-04.24.31:103][ 0]LogTemp: Warning: UMyTestObj::BeginDestroy() [2024.09.29-04.24.31:794][ 1]LogTemp: Warning: WeakTestObj is - Invalid
Outer 가 Valid 하지만 Referencing 이 없으니 파괴 된다.
이전 시나리오와 동일 하다.
class AEkUe54GameMode : public AGameModeBase
{
GENERATED_BODY()
protected:
UPROPERTY()
TObjectPtr<class UMyTestObj> KeepTestObj;
TWeakObjectPtr<class UMyTestObj> WeakTestObj;
public:
AEkUe54GameMode();
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
virtual void BeginDestroy() override;
};
void AEkUe54GameMode::BeginPlay()
{
Super::BeginPlay();
{
UMyTestObj* MyTestObj = NewObject<UMyTestObj>(this);
WeakTestObj = MyTestObj;
UMyTestObj* MyTestSubObj = NewObject<UMyTestObj>(MyTestObj);
KeepTestObj = MyTestSubObj;
GEngine->ForceGarbageCollection(true);
}}
void AEkUe54GameMode::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
UE_LOG(LogTemp, Warning, TEXT("WeakTestObj is - %s"), WeakTestObj.IsValid() ? TEXT("Valid") : TEXT("Invalid") );
}
void AEkUe54GameMode::BeginDestroy()
{
UE_LOG(LogTemp, Warning, TEXT("AEkUe54GameMode::BeginDestroy() - %s"), *GetName());
Super::BeginDestroy();
}
MyTestSubObj 를 만들고 Outer 로 이전에 GC되고 있는 상태였던 MyTestObj 를 지정해 주었다. 그리고 AEkUe54GameMode 에서 MyTestSubObj 로의 Referencing 을 유지 하고 있다.
[2024.09.29-04.54.31:639][324]LogTemp: Warning: WeakTestObj is - Valid
MyTestObj 로의 Referencing 이 AEkUe54GameMode 로부터 걸려있지 않아도 잘 살아 있다. 이는 SubObject 인 MyTestSubObj 로 Referencing 이 걸려있고 MyTestSubObj 에서 Outer 인 MyTestObj 로 Referencing 이 걸리기 때문이다.
Outer 는 SubObject 의 GC 를 막지 않는다(Referencing 하지 않는다). 오히려 SubObject 로부터 Outer 로 Referencing 이 걸린다.