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");
    }
}

ScriptableObjectInstallerMonoInstaller此方面的工作方式相同。

在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()方法
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345