UE4 static 变量 GC 导致闪退问题

最近开发一个 UMG 插件,插件里用到了静态变量,在 UE4 编辑器里运行没有啥问题,然后打了手机包iOS后,运行一段时间后,闪退了,闪退点就是我写的代码里中访问全局变量的时候,使用 UnrealVS 插件在 VS2019 下开启 Window 模式,也出现了类似的闪退问题:

UnrealVS 参数

“E:\xxx.uproject” -game -windowed -log -verbose

闪退代码处:

// .h 文件
UCLASS()
class UMGEXT_API UExtTextBlock : public UTextBlock
{
    static UDataTable* ExtTextStyleSet;
}

// .cpp 文件
UDataTable* UExtTextBlock::ExtTextStyleSet = nullptr;

void UExtTextBlock::EnsureTextDataTable()
{
    if (nullptr == ExtTextStyleSet || !ExtTextStyleSet->IsValidLowLevel())
    {
        ExtTextStyleSet = LoadObject<UDataTable>(nullptr, *DataPath);
        ExtTextStyleSet->AddToRoot();
    }
}

FTextBlockStyle UExtTextBlock::GetTextStyleByName(FName StyleName)
{
    EnsureTextDataTable();
    FTextBlockStyle Style = FTextBlockStyle::GetDefault();
    if(nullptr == UExtTextBlock::ExtTextStyleSet)
    {
        return Style;
    }

    /// 闪退地方
    auto RowMap = UExtTextBlock::ExtTextStyleSet->GetRowMap();
    if(RowMap.Contains(StyleName))
    {
        FRichTextStyleRow* RichTextStyle = (FRichTextStyleRow*)(RowMap[StyleName]);
        Style = RichTextStyle->TextStyle;
    }
    return Style;
}

用 VS2019 调试闪退点,查看 UExtTextBlock::ExtTextStyleSet 内存,每次都不一样,而且类型对象完全不匹配,所以联想到可能是内存空间被GC,然后之前指向的内存被其他变量占用了,于是去谷歌。

要防止对象被GC,有4种方式:

  • 作为成员变量并标记为UPROPERTY();
  • 创建对象后 AddToRoot() ;(退出游戏时需要RemoveFromRoot())
  • FStreamableManager Load资源时,bManageActiveHandle 设置为true;
  • FGCObjectScopeGuard 在指定代码区域内保持对象;

注意:
一个UObject类型的变量,即使是static,默认也会被GC掉。

知道了原因,就好解决问题了,于是做了一下修改:

/// 插件模块代码 
/// 启动时: 加载创建静态变量,并且将 静态变量加到 Root 上
void FUMGExtModule::StartupModule()
{
    UE_LOG(LogUMGExtModule, Display, TEXT("UMGExtModule StartupModule"));

    UExtTextBlock::EnsureTextDataTable();
}

/// 模块卸载时:将静态变量从 Root 拿掉
void FUMGExtModule::ShutdownModule()
{
    UE_LOG(LogUMGExtModule, Display, TEXT("UMGExtModule ShutdownModule"));

    UExtTextBlock::ClearTextDataTable();
}

/// 对应类的代码
void UExtTextBlock::EnsureTextDataTable()
{
    if (nullptr == ExtTextStyleSet || !ExtTextStyleSet->IsValidLowLevel())
    {
        ExtTextStyleSet = LoadObject<UDataTable>(nullptr, *DataPath);
        /// AddToRoot : 防止被 GC
        ExtTextStyleSet->AddToRoot();
    }
}

void UExtTextBlock::ClearTextDataTable()
{
    if (nullptr != ExtTextStyleSet)
    {
        /// RemoveFromRoot : 可以 GC 了 
        ExtTextStyleSet->RemoveFromRoot();
    }
}