一、代理的分類與概括
-
何爲"動態"代理?
作者認爲"動態"主要體現在 其使用UFUNCTION的函數名綁定。即是在運行期通過反射機制動態
的根據函數名查找函數,然後執行的。 -
代理的一些 設定
- 支持返回值
- 支持綁定最多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 中的說明
-
聲明代理
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. 常用宏
- 動態單播
宏 | 意義 |
---|---|
BindDynamic( UserObject, FuncName ) | 綁定 |
- 多播
宏 | 意義 |
---|---|
AddDynamic( UserObject, FuncName ) | 增加代理 |
AddUniqueDynamic( UserObject, FuncName ) | 增加代理 去重 |
RemoveDynamic( UserObject, FuncName ) | 移除代理 |
IsAlreadyBound( UserObject, FuncName ) | 是否已經綁定代理 |
2. 示例
- 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. 代理的執行是否安全的分析
- 標記 [安全] 的綁定方式,其實現上代理持有對象的弱引用,可以通過檢查對象的狀態來確定是否可以安全執行。
- 標記 [非安全] 的綁定方式, 其實現上是 沒有辦法確定 對象是否過期 (雖然也有 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的成員 分爲 BindUFunction
和 BindUObject
兩種API.
前者是通過名字動態綁定的,故要綁定的函數要用UFUNCTION()
標記。
後者是通過成員函數指針綁定的,故加不加UFUNCTION()
標記 都可以。
待續 … 後續有新的發現 再完善…