开放泛型类型(Open Generic Types)
Zenject还有一个功能,允许您在注入期间自动填写开放的泛型参数。 例如:
public class Bar<T>
{
public int Value
{
get; set;
}
}
public class Foo
{
public Foo(Bar<int> bar)
{
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public override void InstallBindings()
{
Container.Bind(typeof(Bar<>)).AsSingle();
Container.Bind<Foo>().AsSingle().NonLazy();
}
}
请注意,在使用开放泛型参数绑定类型时,必须使用Bind()方法的非泛型版本。 正如您在示例中所看到的,当绑定开放的泛型类型时,它将匹配注入的参数/字段/属性所需的任何内容。 您还可以将一个开放泛型类型绑定到另一个开放泛型类型,如下所示:
public interface IBar<T>
{
int Value
{
get; set;
}
}
public class Bar<T> : IBar<T>
{
public int Value
{
get; set;
}
}
public class Foo
{
public Foo(IBar<int> bar)
{
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public override void InstallBindings()
{
Container.Bind(typeof(IBar<>)).To(typeof(Bar<>)).AsSingle();
Container.Bind<Foo>().AsSingle().NonLazy();
}
}
这有时会带来一些有趣的设计可能性,让人很容易意识到。
关于Destruction(销毁)/Dispose(处理)顺序的注意事项
如果为实现IDisposable
的类添加绑定,则可以通过设置执行顺序来控制它们的Dispose(处理)顺序。但是,场景中的GameObjects不是这种情况。
Unity有一个“脚本执行顺序”的概念,但是这个值不会影响OnDestroy的执行顺序。根级游戏对象可能以任何顺序被销毁,这也包括SceneContext。
使其更具可预测性的一种方法是将所有内容放在SceneContext下面。对于需要确定性的破坏顺序的情况这可能非常有用,因为它至少可以保证在场景中的任何游戏对象之前绑定IDisposable的首先Dispose(处理)。您还可以在场景上下文(SceneContext)的“Parent New Objects Under Scene Context”上切换设置,以便自动为场景上下文(SceneContext)下的所有动态实例化对象设置父级。
在销毁顺序方面有时会出现的另一个问题是卸载场景的顺序以及卸载DontDestroyOnLoad对象(包括ProjectContext)的顺序。
不幸的是,在这种情况下,Unity也不保证确定性的销毁顺序,你会发现有时在退出应用程序时,DontDestroyOnLoad的对象实际上在销毁场景之前被销毁,或者你会发现首先加载的场景也是首先被摧毁,而这通常不是你想要的。
如果场景销毁顺序对您很重要,那么您可以考虑更改ZenjectSetting的Ensure Deterministic Destruction Order On Application Quit
为true。当设为true时,所有场景在OnApplicationQuit期间都将被强制销毁,销毁的顺序比unity默认的顺序更合理。它将首先按照加载顺序的相反顺序销毁所有场景(以便稍后销毁之前加载的场景),最后通过销毁包含项目上下文(project context)的DontDestroyOnLoad对象完成。
该项没有默认设为true的原因是它可能会导致Android崩溃,如此处(https://github.com/modesttree/Zenject/issues/301)所述。
UniRx集成
UniRx是一个为Unity带来Reactive Extensions的库。 它可以通过将类之间的某些通信视为数据的“流”来大大简化您的代码。 有关更多详细信息,请参阅UniRx文档。
默认情况下禁用Zenject与UniRx的集成。 要启用,必须将定义ZEN_SIGNALS_ADD_UNIRX
添加到项目中,您可以通过选择Edit -> Project Settings -> Player
,然后在“Scripting Define Symbols”部分中添加ZEN_SIGNALS_ADD_UNIRX
来执行此操作
使用zenject版本7.0.0,您还必须将Zenject.asmdef文件更改为以下内容:
{
"name": "Zenject",
"references": [
"UniRx"
]
}
启用ZEN_SIGNALS_ADD_UNIRX
后,您可以通过UniRx流观察zenject信号(zenject signals),如信号文档中所述,您还可以在TickableManager类上观察zenject事件,如Tick,LateTick和FixedTick等。 下面是一个确保某些事件每帧最多只处理一次的示例用法:
public class User
{
public string Username;
}
public class UserManager
{
readonly List<User> _users = new List<User>();
readonly Subject<User> _userAddedStream = new Subject<User>();
public IReadOnlyList<User> Users
{
get { return _users; }
}
public IObservableRx<User> UserAddedStream
{
get { return _userAddedStream; }
}
public void AddUser(User user)
{
_users.Add(user);
_userAddedStream.OnNext(user);
}
}
public class UserDisplayWindow : IInitializable, IDisposable
{
readonly TickableManager _tickManager;
readonly CompositeDisposable _disposables = new CompositeDisposable();
readonly UserManager _userManager;
public UserDisplayWindow(
UserManager userManager,
TickableManager tickManager)
{
_tickManager = tickManager;
_userManager = userManager;
}
public void Initialize()
{
_userManager.UserAddedStream.Sample(_tickManager.TickStream)
.Subscribe(x => SortView()).AddTo(_disposables);
}
void SortView()
{
// 对展示的用户列表排序
}
public void Dispose()
{
_disposables.Dispose();
}
}
在该例中,我们有一些耗能的操作,我们希望每次在某些数据更改时运行这些操作(在该例中是排序),它所做的只是影响某些内容的呈现方式(在该例中是显示用户名列表)。 我们可以实现ITickable接口,然后在每次数据更改时设置一个布尔标志,然后在Tick()内部执行更新,但这并不是真正的响应式的做法,所以我们使用Sample()代替。
使用Moq自动模拟(Auto-Mocking using Moq)
见后续章节
网友评论