UE4 代理 使用與總結

一、代理的分類與概括

在這裏插入圖片描述

  1. 何爲"動態"代理?
    作者認爲"動態"主要體現在 其使用UFUNCTION的函數名綁定。即是在運行期通過反射機制動態的根據函數名查找函數,然後執行的。

  2. 代理的一些 設定

    • 支持返回值
    • 支持綁定最多4個額外的變量
      static void MyFunc(FString str, bool b, int32 i){}
      DECLARE_DELEGATE_OneParam(FTestDel, FString);
      
      void func()
      {
      	FTestDel testDel;
      	testDel.BindStatic(&MyFunc, true, 1);
      	testDel.ExecuteIfBound(TEXT("aaaaa"));
      }
      
    • 支持最多8個函數參數
    • 支持綁定const函數

    可參看 Engine\Source\Runtime\Core\Public\Delegates\Delegate.h 中的說明

  3. 聲明代理

    void Function()										|	DECLARE_DELEGATE( DelegateName )
    void Function( <Param1> )							|	DECLARE_DELEGATE_OneParam( DelegateName, Param1Type )
    void Function( <Param1>, <Param2> )					|	DECLARE_DELEGATE_TwoParams( DelegateName, Param1Type, Param2Type )
    void Function( <Param1>, <Param2>, ... )			|	DECLARE_DELEGATE_<Num>Params( DelegateName, Param1Type, Param2Type, ... )
    <RetVal> Function()									|	DECLARE_DELEGATE_RetVal( RetValType, DelegateName )
    <RetVal> Function( <Param1> )						|	DECLARE_DELEGATE_RetVal_OneParam( RetValType, DelegateName, Param1Type )
    <RetVal> Function( <Param1>, <Param2> )				|	DECLARE_DELEGATE_RetVal_TwoParams( RetValType, DelegateName, Param1Type, Param2Type )
    <RetVal> Function( <Param1>, <Param2>, ... )		|	DECLARE_DELEGATE_RetVal_<Num>Params( RetValType, DelegateName, Param1Type, Param2Type, ... )
    
     Single-cast delegates:  DECLARE_DELEGATE...()
     Multi-cast delegates:  DECLARE_MULTICAST_DELEGATE...()
     Dynamic (UObject, serializable) delegates:  DECLARE_DYNAMIC_DELEGATE...()
    

二、動態代理

1. 常用宏

  1. 動態單播
意義
BindDynamic( UserObject, FuncName ) 綁定
  1. 多播
意義
AddDynamic( UserObject, FuncName ) 增加代理
AddUniqueDynamic( UserObject, FuncName ) 增加代理 去重
RemoveDynamic( UserObject, FuncName ) 移除代理
IsAlreadyBound( UserObject, FuncName ) 是否已經綁定代理

2. 示例

  1. CPP
class AMyProjectGameMode : public AGameModeBase
{
	GENERATED_BODY()
public:
	AMyProjectGameMode();
public:
	DECLARE_DYNAMIC_DELEGATE(FOnComplate);
	DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTest);
	
	UPROPERTY(BlueprintAssignable)
	FOnTest onTestDyMulticast;

	UFUNCTION(BlueprintCallable)
	void testSinglecastFunc(const FOnComplate& complateCall)
	{
		//! 測試綁定
		onTestDyMulticast.AddDynamic(this, &AMyProjectGameMode::onTestDyMult);
	
		//! 測試調用
		onTestDyMulticast.Broadcast();

		//! 測試單播調用

		if (complateCall.IsBound())
		{
			complateCall.Execute();
		}

		//! 注意  只有 無返回值的 代理 纔有  ExecuteIfBound
		complateCall.ExecuteIfBound();
	}

private:

	UFUNCTION()
	void onTestDyMult()
	{
	}
};

2. 藍圖
在這裏插入圖片描述

三、普通代理

以單播爲例,多播的綁定類似

綁定函數 說明
BindStatic 綁定普通函數
BindLambda 綁定Lambda [安全]
BindWeakLambda 綁定Lambda 並且持有 Uobject的弱引用 [安全]
BindRaw 綁定 原生的C++類成員 [非安全]
BindSP 綁定 智能指針對象的成員 [安全]
BindThreadSafeSP 綁定 (智能指針線程安全的版本)對象的成員 [安全]
BindUFunction 綁定UFUNCTION [通過反射運行時動態查找] [安全]
BindUObject 綁定一個UObject [安全]

1. 代理的執行是否安全的分析

  1. 標記 [安全] 的綁定方式,其實現上代理持有對象的弱引用,可以通過檢查對象的狀態來確定是否可以安全執行。
  2. 標記 [非安全] 的綁定方式, 其實現上是 沒有辦法確定 對象是否過期 (雖然也有 IsSafeToExecute 但是 是直接返回true)

以綁定 Lambda 爲例

UCLASS()
class UTestObject : public UObject
{
	GENERATED_BODY()

public:

	~UTestObject()
	{
		testNum = nullptr;
	}
public:
	TSharedPtr<int32> testNum;
};

// CPP

DECLARE_DELEGATE(FTestDel);


void TestExe(UObject* c)
{

	UTestObject* obj = NewObject<UTestObject>(c);
	obj->testNum = MakeShared<int32>(1000);


	FTestDel onTestDel_weak;
	FTestDel onTestDel;

	onTestDel.BindLambda([obj]() {

		// obj do 
		*obj->testNum = 3434;

	});

	onTestDel_weak.BindWeakLambda(obj, [obj]() {

		// obj do 
		*obj->testNum = 3434;

	});

	obj = nullptr;

	GEngine->ForceGarbageCollection();

	// 等GC完成後執行
	FTimerHandle h;
	c->GetWorld()->GetTimerManager().SetTimer(h,[onTestDel_weak, onTestDel]() {
	
		//OK  lambda 中的obj 已經釋放  所以不會被執行 
		if (onTestDel_weak.IsBound())
		{
			onTestDel_weak.Execute();
		}

		//NO lambda 中的obj 以及釋放  但是 還是會被執行 
		if (onTestDel.IsBound())
		{
			onTestDel.Execute();
		}
	
	}, 1.0f, false);
}

這裏的安全是指在 執行代理時進行是否可以安全的判斷,然後再執行。如果直接執行也是會有一定的異常風險的。

如果代理 的函數簽名是沒有返回值的 void(arg),那麼代理擁有 ExecuteIfBound() 這個便捷的執行方法。

2. 代理綁定智能指針


struct FTestSharedPtr 
{
	void test()
	{
	}
};

void testDel()
{
	TSharedPtr<FTestSharedPtr> p = MakeShared<FTestSharedPtr>();
	FTestDel del;
	del.BindSP(p.ToSharedRef(), &FTestSharedPtr::test);

	// 用原始指針
	del.BindSP(p.Get(), &FTestSharedPtr::test);
}

上述代碼會編譯出錯,綁定函數提供了通過原始指針綁定的重載,但是其有個前提是 對象必須是繼承 TSharedFromThis<T> 。大多數情況下用在綁定對象自身的時候。如下:

struct FTestSharedPtr
	:public TSharedFromThis<FTestSharedPtr>
{
	void test()
	{
	}
	void bind(FTestDel& del)
	{
		del.BindSP(this, &FTestSharedPtr::test);
	};
};

3. 代理綁定UObject

綁定一個UObject的成員 分爲 BindUFunctionBindUObject 兩種API.
前者是通過名字動態綁定的,故要綁定的函數要用UFUNCTION()標記。
後者是通過成員函數指針綁定的,故加不加UFUNCTION()標記 都可以。


待續 … 後續有新的發現 再完善…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章