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이 걸립니다.