Zenject框架(十)
安装器的运行时参数(Runtime Parameters For Installers)
通常在从安装器调用其他安装器时,希望能够传递参数。您可以通过将通用参数添加到正在使用的任何安装器基类以及运行时参数的类型来执行此操作。例如,使用非MonoBehaviour安装器时:
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预制件时:
public class FooInstaller : MonoInstaller<string, FooInstaller>
{
string _value;
// 注意这种情况下我们不能使用构造函数
[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()
{
// 为使其正常工作,必须在存在以下预设体`Resources / My / Custom / ResourcePath.prefab`。
//且该预设体上有FooInstaller组件
FooInstaller.InstallFromResource("My/Custom/ResourcePath", Container, "asdf")
// 如果未提供资源路径,则假定它存在于资源路径
// 'Resources/Installers/FooInstaller'下
// 比如:
// FooInstaller.InstallFromResource(Container, "asdf");
}
}
ScriptableObjectInstaller
与MonoInstaller
此方面的工作方式相同。
在Unity外部或者Dll中使用Zenject
如果您正在构建一些代码作为DLL然后将它们包含在Unity中,您仍然可以在安装器中为这些类添加绑定,唯一的限制是您必须使用构造函数注入。如果您想使用其他注入方法,例如成员注入或方法注入,那么您也可以这样做,但是在这种情况下,您需要为项目添加一个Zenject-Usage.dll
,该文件可以在Zenject\Source\Usage
目录中找到。此DLL还包括标准接口ITickable
, IInitializable
等,因此您也可以使用它们。
您还可以通过下载Zenject-NonUnity.zip
在非Unity项目中使用zenject
最后,如果您尝试使用Unity生成的解决方案在Unity外部运行单元测试,在运行时,在Zenject代码尝试访问Unity API时中可能会遇到错误。您可以通过在生成的解决方案中添加ZEN_TESTS_OUTSIDE_UNITY
条件编译来禁用此行为。
Zenject设置
可以通过ProjectContext上的settings属性自定义Zenject中的许多默认行为。这包括以下内容:
- Validation Error Response(验证错误响应) - 此值控制zenject遇到验证错误时触发的行为。它可以设置为“Log”或“Throw”。这里的区别在于,当设置为“Log”时,每次运行验证时都会打印多个验证错误,而如果设置为“Throw”,则只会将第一个验证错误输出到控制台。取消设置时,默认值为“Log”。如果在单元测试中运行验证,“Throw”有时也很有用。
- Validation Root Resolve Method (验证根解析方法) - 当为给定场景触发验证时,DiContainer将执行“干运行”并假装实例化场景中安装程序定义的整个对象图。但是,默认情况下,它只会验证对象图的“根” - 即“NonLazy”绑定或注入“NonLazy”绑定的绑定。作为选项,您可以将此行为更改为“全部”,这将验证所有绑定,甚至是那些当前未使用的绑定。
- Display Warning When Resolving During Install(在安装期间解析时显示警告) - 此值将控制在安装阶段触发Resolve或Instantiate时是否向控制台发出警告,如下所示:
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'.
因此,如果您经常遇到此警告并且意识到您正在执行的操作的含义,那么您可以将此值设置为false以禁止它。
- Ensure Deterministic Destruction Order On Application Quit(确认应用程序退出时的确定性销毁顺序) - 设置为true时,这将确认在应用程序关闭时以可预测的顺序销毁所有GameObject和IDisposable。默认情况下,它设置为false,因为如本节所述,启用此功能会产生一些不良后果。
信号(Signals)
详见“Zenject框架(十八)- 信号”章节
使用工厂动态创建对象(Creating Objects Dynamically Using Factories)
详见后续信号章节
内存池(Memory Pools)
详见后续信号章节
更新/初始化顺序(Update / Initialization Order)
在许多情况下,特别是对于小型项目,类更新或初始化的顺序无关紧要。但是,在较大的项目中,更新或初始化顺序要认真对待。这在Unity中尤其明显,因为它往往很难预知多个Start(),Awake()或Update()以何种顺序被调用。不幸的是,Unity没有一种简单的方法控制这种情况(除了Edit -> Project Settings -> Script Execution Order
,虽然这用起来可能很尴尬)
在Zenject中,默认情况下,多个ITickable和IInitializable按照添加顺序进行调用,但是对于对更新或初始化顺序有要求的情况,还有另一种方法有时更好:通过在安装器中明确指定其优先级。例如,在示例项目中,您可以在场景安装器中找到此代码:
public class AsteroidsInstaller : MonoInstaller
{
...
void InitExecutionOrder()
{
// 很多情况下不需要关心执行顺序
// 但在另一些情况下执行顺序很重要
// 例如,我们要求AsteroidManager.Initialize
// 总是在GameController.Initialize(以及Tick)之前调用
// 我们可以这样做:
Container.BindExecutionOrder<AsteroidManager>(-10);
Container.BindExecutionOrder<GameController>(-20);
// 注意销毁时以相反顺序进行
}
...
public override void InstallBindings()
{
...
InitExecutionOrder();
...
}
}
这样,就不会因为不可预知的依赖项顺序导致错误发生。
请注意,给定BindExecutionOrder的值将适用于ITickable/ IInitializable和IDisposable(对于IDisposable为反向顺序)。
您还可以分别为每个特定接口分配优先级,如下所示:
Container.BindInitializableExecutionOrder<Foo>(-10);
Container.BindInitializableExecutionOrder<Bar>(-20);
Container.BindTickableExecutionOrder<Foo>(10);
Container.BindTickableExecutionOrder<Bar>(-80);
任何未分配优先级的ITickables,IInitializable或IDisposable都会自动优先为零。这允许您在未指定的类之前或之后执行具有显式优先级的类。例如,上面的代码将导致Foo.Initialize在Bar.Initialize之前被调用。
Zenject运行顺序
下面是运行使用Zenject的场景时会发生什么情况的更详细的视图。完全理解Zenject的工作原理可能很有用。
- Unity Awake()阶段开始
- 调用SceneContext.Awake()方法。这应该始终是场景中执行的第一件事。默认情况下它也以这种方式工作(如果出现异常,参见“一般指南/建议/陷阱/提示和技巧”最后一条)。
- 项目上下文(Project Context)初始化。请注意,每次运行都只会发生一次。如果前一个场景已初始化ProjectContext,则跳过此步骤。
- ProjectContext预制体上的所有可注射的MonoBehaviour都通过DiContainer.QueueForInject传递给容器
- ProjectContext遍历通过Unity检视面板添加到其预制体的所有安装器(installer),运行它们的注入,然后在每个安装器上调用InstallBindings()。每个安装器在DiContainer上都会调用一些Bind方法。
- 然后,ProjectContext构造所有非延迟的根对象,其中包括从ITickable / IInitializable或IDisposable派生的任何类,以及使用NonLazy()绑定添加的那些类。
- 注入通过DiContainer.QueueForInject添加的所有实例
- 场景上下文(SceneContext)初始化
- 所有可注入的MonoBehaviour都通过DiContainer.QueueForInject传递给场景上下文的容器
- SceneContext遍历通过Unity检视面板添加到其上的所有安装器(installer),运行它们的注入,然后在每个安装器上调用InstallBindings()。每个安装器在DiContainer上都会调用一些Bind<>方法。
- 然后,ProjectContext构造所有非延迟的根对象,其中包括从ITickable / IInitializable或IDisposable派生的任何类,以及使用NonLazy()绑定添加的那些类。
- 注入通过DiContainer.QueueForInject添加的所有实例
- 如果无法解析某些依赖项,zenject抛出异常
- 场景中其他的Monobehaviour调用自身的Awake() 方法
- Unity Start()阶段开始
- 调用ProjectKernel.Start()方法。这将以在ProjectContext安装器中指定的顺序触发所有IInitializable对象的Initialize()方法。
- 调用SceneKernel.Start()方法。这将以在SceneContext安装器中指定的顺序触发所有IInitializable对象的Initialize()方法。
- 场景中所有其他的MonoBehaviour调用自身的Start()方法
- Unity Update() 阶段开始
- 调用ProjectKernel.Update()方法,这会导致为所有ITickable对象调用Tick()(按照ProjectContext安装器中指定的顺序)
- 调用SceneKernel.Update(),导致为所有ITickable对象调用Tick()(按照SceneContext安装器中指定的顺序)
- 场景中的所有其他MonoBehaviour调用自身Update()方法
- 对LateUpdate和ILateTickable重复这些相同的步骤
- 同时,根据物理时间步长,对FixedUpdate重复这些相同的步骤
- Unity场景被卸载
- 调用所有游戏对象上下文(GameObjectContext)中的Dispose()方法
- 调用场景上下文(SceneContext)安装器中的Dispose()方法
- 退出程序
- 调用工程 上下文(ProjectContext)安装器中的Dispose()方法