安裝器的運(yùn)行時(shí)參數(shù)(Runtime Parameters For Installers)
通常在從安裝器調(diào)用其他安裝器時(shí),希望能夠傳遞參數(shù)。您可以通過將通用參數(shù)添加到正在使用的任何安裝器基類以及運(yùn)行時(shí)參數(shù)的類型來執(zhí)行此操作。例如,使用非MonoBehaviour安裝器時(shí):
public class FooInstaller : Installer<string, FooInstaller>
{
string _value;
public FooInstaller(string value)
{
_value = value;
}
public override void InstallBindings()
{
...
Container.BindInstance(_value).WhenInjectedInto<Foo>();
}
}
public class MainInstaller : MonoInstaller
{
public override void InstallBindings()
{
FooInstaller.Install(Container, "asdf");
}
}
或者在使用MonoInstaller預(yù)制件時(shí):
public class FooInstaller : MonoInstaller<string, FooInstaller>
{
string _value;
// 注意這種情況下我們不能使用構(gòu)造函數(shù)
[Inject]
public void Construct(string value)
{
_value = value;
}
public override void InstallBindings()
{
...
Container.BindInstance(_value).WhenInjectedInto<Foo>();
}
}
public class MainInstaller : MonoInstaller
{
public override void InstallBindings()
{
// 為使其正常工作,必須在存在以下預(yù)設(shè)體`Resources / My / Custom / ResourcePath.prefab`。
//且該預(yù)設(shè)體上有FooInstaller組件
FooInstaller.InstallFromResource("My/Custom/ResourcePath", Container, "asdf")
// 如果未提供資源路徑,則假定它存在于資源路徑
// 'Resources/Installers/FooInstaller'下
// 比如:
// FooInstaller.InstallFromResource(Container, "asdf");
}
}
ScriptableObjectInstaller與MonoInstaller此方面的工作方式相同。
在Unity外部或者Dll中使用Zenject
如果您正在構(gòu)建一些代碼作為DLL然后將它們包含在Unity中,您仍然可以在安裝器中為這些類添加綁定,唯一的限制是您必須使用構(gòu)造函數(shù)注入。如果您想使用其他注入方法,例如成員注入或方法注入,那么您也可以這樣做,但是在這種情況下,您需要為項(xiàng)目添加一個(gè)Zenject-Usage.dll,該文件可以在Zenject\Source\Usage目錄中找到。此DLL還包括標(biāo)準(zhǔn)接口ITickable, IInitializable等,因此您也可以使用它們。
您還可以通過下載Zenject-NonUnity.zip在非Unity項(xiàng)目中使用zenject
最后,如果您嘗試使用Unity生成的解決方案在Unity外部運(yùn)行單元測試,在運(yùn)行時(shí),在Zenject代碼嘗試訪問Unity API時(shí)中可能會(huì)遇到錯(cuò)誤。您可以通過在生成的解決方案中添加ZEN_TESTS_OUTSIDE_UNITY條件編譯來禁用此行為。
Zenject設(shè)置
可以通過ProjectContext上的settings屬性自定義Zenject中的許多默認(rèn)行為。這包括以下內(nèi)容:
- Validation Error Response(驗(yàn)證錯(cuò)誤響應(yīng)) - 此值控制zenject遇到驗(yàn)證錯(cuò)誤時(shí)觸發(fā)的行為。它可以設(shè)置為“Log”或“Throw”。這里的區(qū)別在于,當(dāng)設(shè)置為“Log”時(shí),每次運(yùn)行驗(yàn)證時(shí)都會(huì)打印多個(gè)驗(yàn)證錯(cuò)誤,而如果設(shè)置為“Throw”,則只會(huì)將第一個(gè)驗(yàn)證錯(cuò)誤輸出到控制臺。取消設(shè)置時(shí),默認(rèn)值為“Log”。如果在單元測試中運(yùn)行驗(yàn)證,“Throw”有時(shí)也很有用。
- Validation Root Resolve Method (驗(yàn)證根解析方法) - 當(dāng)為給定場景觸發(fā)驗(yàn)證時(shí),DiContainer將執(zhí)行“干運(yùn)行”并假裝實(shí)例化場景中安裝程序定義的整個(gè)對象圖。但是,默認(rèn)情況下,它只會(huì)驗(yàn)證對象圖的“根” - 即“NonLazy”綁定或注入“NonLazy”綁定的綁定。作為選項(xiàng),您可以將此行為更改為“全部”,這將驗(yàn)證所有綁定,甚至是那些當(dāng)前未使用的綁定。
- Display Warning When Resolving During Install(在安裝期間解析時(shí)顯示警告) - 此值將控制在安裝階段觸發(fā)Resolve或Instantiate時(shí)是否向控制臺發(fā)出警告,如下所示:
Zenject Warning: It is bad practice to call Inject/Resolve/Instantiate before all the Installers have completed! This is important to ensure that all bindings have properly been installed in case they are needed when injecting/instantiating/resolving. Detected when operating on type 'Foo'.
因此,如果您經(jīng)常遇到此警告并且意識到您正在執(zhí)行的操作的含義,那么您可以將此值設(shè)置為false以禁止它。
- Ensure Deterministic Destruction Order On Application Quit(確認(rèn)應(yīng)用程序退出時(shí)的確定性銷毀順序) - 設(shè)置為true時(shí),這將確認(rèn)在應(yīng)用程序關(guān)閉時(shí)以可預(yù)測的順序銷毀所有GameObject和IDisposable。默認(rèn)情況下,它設(shè)置為false,因?yàn)槿绫竟?jié)所述,啟用此功能會(huì)產(chǎn)生一些不良后果。
信號(Signals)
詳見“Zenject框架(十八)- 信號”章節(jié)
使用工廠動(dòng)態(tài)創(chuàng)建對象(Creating Objects Dynamically Using Factories)
詳見后續(xù)信號章節(jié)
內(nèi)存池(Memory Pools)
詳見后續(xù)信號章節(jié)
更新/初始化順序(Update / Initialization Order)
在許多情況下,特別是對于小型項(xiàng)目,類更新或初始化的順序無關(guān)緊要。但是,在較大的項(xiàng)目中,更新或初始化順序要認(rèn)真對待。這在Unity中尤其明顯,因?yàn)樗茈y預(yù)知多個(gè)Start(),Awake()或Update()以何種順序被調(diào)用。不幸的是,Unity沒有一種簡單的方法控制這種情況(除了Edit -> Project Settings -> Script Execution Order,雖然這用起來可能很尷尬)
在Zenject中,默認(rèn)情況下,多個(gè)ITickable和IInitializable按照添加順序進(jìn)行調(diào)用,但是對于對更新或初始化順序有要求的情況,還有另一種方法有時(shí)更好:通過在安裝器中明確指定其優(yōu)先級。例如,在示例項(xiàng)目中,您可以在場景安裝器中找到此代碼:
public class AsteroidsInstaller : MonoInstaller
{
...
void InitExecutionOrder()
{
// 很多情況下不需要關(guān)心執(zhí)行順序
// 但在另一些情況下執(zhí)行順序很重要
// 例如,我們要求AsteroidManager.Initialize
// 總是在GameController.Initialize(以及Tick)之前調(diào)用
// 我們可以這樣做:
Container.BindExecutionOrder<AsteroidManager>(-10);
Container.BindExecutionOrder<GameController>(-20);
// 注意銷毀時(shí)以相反順序進(jìn)行
}
...
public override void InstallBindings()
{
...
InitExecutionOrder();
...
}
}
這樣,就不會(huì)因?yàn)椴豢深A(yù)知的依賴項(xiàng)順序?qū)е洛e(cuò)誤發(fā)生。
請注意,給定BindExecutionOrder的值將適用于ITickable/ IInitializable和IDisposable(對于IDisposable為反向順序)。
您還可以分別為每個(gè)特定接口分配優(yōu)先級,如下所示:
Container.BindInitializableExecutionOrder<Foo>(-10);
Container.BindInitializableExecutionOrder<Bar>(-20);
Container.BindTickableExecutionOrder<Foo>(10);
Container.BindTickableExecutionOrder<Bar>(-80);
任何未分配優(yōu)先級的ITickables,IInitializable或IDisposable都會(huì)自動(dòng)優(yōu)先為零。這允許您在未指定的類之前或之后執(zhí)行具有顯式優(yōu)先級的類。例如,上面的代碼將導(dǎo)致Foo.Initialize在Bar.Initialize之前被調(diào)用。
Zenject運(yùn)行順序
下面是運(yùn)行使用Zenject的場景時(shí)會(huì)發(fā)生什么情況的更詳細(xì)的視圖。完全理解Zenject的工作原理可能很有用。
- Unity Awake()階段開始
- 調(diào)用SceneContext.Awake()方法。這應(yīng)該始終是場景中執(zhí)行的第一件事。默認(rèn)情況下它也以這種方式工作(如果出現(xiàn)異常,參見“一般指南/建議/陷阱/提示和技巧”最后一條)。
- 項(xiàng)目上下文(Project Context)初始化。請注意,每次運(yùn)行都只會(huì)發(fā)生一次。如果前一個(gè)場景已初始化ProjectContext,則跳過此步驟。
- ProjectContext預(yù)制體上的所有可注射的MonoBehaviour都通過DiContainer.QueueForInject傳遞給容器
- ProjectContext遍歷通過Unity檢視面板添加到其預(yù)制體的所有安裝器(installer),運(yùn)行它們的注入,然后在每個(gè)安裝器上調(diào)用InstallBindings()。每個(gè)安裝器在DiContainer上都會(huì)調(diào)用一些Bind方法。
- 然后,ProjectContext構(gòu)造所有非延遲的根對象,其中包括從ITickable / IInitializable或IDisposable派生的任何類,以及使用NonLazy()綁定添加的那些類。
- 注入通過DiContainer.QueueForInject添加的所有實(shí)例
- 場景上下文(SceneContext)初始化
- 所有可注入的MonoBehaviour都通過DiContainer.QueueForInject傳遞給場景上下文的容器
- SceneContext遍歷通過Unity檢視面板添加到其上的所有安裝器(installer),運(yùn)行它們的注入,然后在每個(gè)安裝器上調(diào)用InstallBindings()。每個(gè)安裝器在DiContainer上都會(huì)調(diào)用一些Bind<>方法。
- 然后,ProjectContext構(gòu)造所有非延遲的根對象,其中包括從ITickable / IInitializable或IDisposable派生的任何類,以及使用NonLazy()綁定添加的那些類。
- 注入通過DiContainer.QueueForInject添加的所有實(shí)例
- 如果無法解析某些依賴項(xiàng),zenject拋出異常
- 場景中其他的Monobehaviour調(diào)用自身的Awake() 方法
- Unity Start()階段開始
- 調(diào)用ProjectKernel.Start()方法。這將以在ProjectContext安裝器中指定的順序觸發(fā)所有IInitializable對象的Initialize()方法。
- 調(diào)用SceneKernel.Start()方法。這將以在SceneContext安裝器中指定的順序觸發(fā)所有IInitializable對象的Initialize()方法。
- 場景中所有其他的MonoBehaviour調(diào)用自身的Start()方法
- Unity Update() 階段開始
- 調(diào)用ProjectKernel.Update()方法,這會(huì)導(dǎo)致為所有ITickable對象調(diào)用Tick()(按照ProjectContext安裝器中指定的順序)
- 調(diào)用SceneKernel.Update(),導(dǎo)致為所有ITickable對象調(diào)用Tick()(按照SceneContext安裝器中指定的順序)
- 場景中的所有其他MonoBehaviour調(diào)用自身Update()方法
- 對LateUpdate和ILateTickable重復(fù)這些相同的步驟
- 同時(shí),根據(jù)物理時(shí)間步長,對FixedUpdate重復(fù)這些相同的步驟
- Unity場景被卸載
- 調(diào)用所有游戲?qū)ο笊舷挛模℅ameObjectContext)中的Dispose()方法
- 調(diào)用場景上下文(SceneContext)安裝器中的Dispose()方法
- 退出程序
- 調(diào)用工程 上下文(ProjectContext)安裝器中的Dispose()方法