-Hello World Example
젠젝트 씬 기본 사용법.
1.하이어라키 Zenject -> Scene Context
2.프로젝트 Create -> Zenject ->MonoInstaller (Ex TestInataller 클래스)
3.2에서 만들어진 스크립트를 게임오브젝트에 달아주고 SceneContext에 연결.
using Zenject; using UnityEngine; using System.Collections; public class TestInstaller : MonoInstaller { public override void InstallBindings() { Container.Bind<string>().FromInstance("Hello World!"); Container.Bind<Greeter>().AsSingle().NonLazy(); } } public class Greeter { public Greeter(string message) { Debug.Log(message); } }
생성자 NonLazy라서 자동으로 호출됨.
SceneContext Monobehavior 은 엔트리 포인트가 됨.
젠젝트가 모든 의존성들을 씬이 시작되기 전에 세팅해줌.
원문 -The SceneContext MonoBehaviour is the entry point of the application, where Zenject sets up all the various dependencies before kicking off your scene. To add content to your Zenject scene, you need to write what is referred to in Zenject as an 'Installer', which declares all the dependencies and their relationships with each other. All dependencies that are marked as "NonLazy" are automatically created after the installers are run, which is why the Greeter class that we added above gets created on startup. If this doesn't make sense to you yet, keep reading!
-Injection
의존성을 주입하는 방법에는 여러가지가 있습니다.
1 - Constructor Injection (생성자를 통한 주입)
public class Foo
{
IBar _bar;
public Foo(IBar bar)
{
_bar = bar;
}
}
2 - Field Injection (필드를 통한 주입)
public class Foo
{
[Inject]
IBar _bar;
}
-Field Injection은 생성자가 호출된 후에 즉시 호출된다.
[Inject]가 표시된 모든 필드는 컨테이너에서 본인의 의존성을 찾고, 찾은 값이 주입된다.
필드가 private인지 public 인지는 상관 없이 의존성 주입이 실행 된다.
3 - Property Injection (프로퍼티를 통한 주입)
public class Foo
{
[Inject]
public IBar Bar
{
get;
private set;
}
}
Property Injection은 필드 인젝션과 같다. setter은 private,public 둘 다 가능하다.
4 - Method Injection (함수를 통한 주입)
public class Foo
{
IBar _bar;
Qux _qux;
[Inject]
public Init(IBar bar, Qux qux)
{
_bar = bar;
_qux = qux;
}
}
Method Injection은 생성자를 통한 주입과 매우 유사하다.
-함수를 통한 주입은 MonoBehaviours를 활용하는 클래스들에서 추천됩니다. 왜냐하면 MonoBehaviours는 생성자가 없기 때문에!
-추천 사용법
Recommendations
Best practice is to prefer constructor/method injection compared to field/property injection.
- Constructor injection forces the dependency to only be resolved once, at class creation, which is usually what you want. In most cases you don't want to expose a public property for your initial dependencies because this suggests that it's open to changing.
- Constructor injection guarantees no circular dependencies between classes, which is generally a bad thing to do. Zenject does allow circular dependencies when using other injections types however such as method/field/property injection
- Constructor/Method injection is more portable for cases where you decide to re-use the code without a DI framework such as Zenject. You can do the same with public properties but it's more error prone (it's easier to forget to initialize one field and leave the object in an invalid state)
- Finally, Constructor/Method injection makes it clear what all the dependencies of a class are when another programmer is reading the code. They can simply look at the parameter list of the method. This is also good because it will be more obvious when a class has too many dependencies and should therefore be split up (since its constructor parameter list will start to seem long)
-Binding
In Zenject, dependency mapping is done by adding bindings to something called a container. The container should then 'know' how to create all the object instances in your application, by recursively resolving all dependencies for a given object.
젠젝트에서, 의존성 매핑은 컨테이너에 바인딩할 무언가를 더하는 식으로 수행된다.
그것들이 수행되고 나면, 컨테이너는 재귀적으로 주어진 오브젝트들의 의존성을 해소함으로써 오브젝트 객체를 어떻게 만들 것인지를 알게된다,
When the container is asked to construct an instance of a given type, it uses C# reflection to find the list of constructor arguments, and all fields/properties that are marked with an [Inject]
attribute. It then attempts to resolve each of these required dependencies, which it uses to call the constructor and create the new instance.
Each Zenject application therefore must tell the container how to resolve each of these dependencies, which is done via Bind commands.
바인딩 옵션들
ContractType = The type that you are creating a binding for.
- This value will correspond to the type of the field/parameter that is being injected.
ResultType = The type to bind to.
- Default: ContractType
- This type must either to equal to ContractType or derive from ContractType. If unspecified, it assumes ToSelf(), which means that the ResultType will be the same as the ContractType. This value will be used by whatever is given as the ConstructionMethod to retrieve an instance of this type
Identifier = The value to use to uniquely identify the binding. This can be ignored in most cases, but can be quite useful in cases where you need to distinguish between multiple bindings with the same contract type. See here for details.
ConstructionMethod = The method by which an instance of ResultType is created/retrieved. See this section for more details on the various construction methods.
- Default: FromNew()
- Examples: eg. FromGetter, FromMethod, FromResolve, FromComponentInNewPrefab, FromSubContainerResolve, FromInstance, etc.
Scope = This value determines how often (or if at all) the generated instance is re-used across multiple injections.
Default: AsTransient. Note however that not all bindings have a default, so an exception will be thrown if not supplied. The bindings that do not require the scope to be set explicitly are any binding with a construction method that is a search rather than creating a new object from scratch (eg. FromMethod, FromComponentX, FromResolve, etc.)
It can be one of the following:
- AsTransient - Will not re-use the instance at all. Every time ContractType is requested, the DiContainer will execute the given construction method again
- AsCached - Will re-use the same instance of ResultType every time ContractType is requested, which it will lazily generate upon first use
- AsSingle - Exactly the same as AsCached, except that it will sometimes throw exceptions if there already exists a binding for ResultType. It is simply a way to ensure that the given ResultType is unique within the container. Note however that it will only guarantee that there is only one instance across the given container, which means that using AsSingle with the same binding in a sub-container could generate a second instance.
In most cases, you will likely want to just use AsSingle, however AsTransient and AsCached have their uses too.
Arguments = A list of objects to use when constructing the new instance of type ResultType. This can be useful as an alternative to adding other bindings for the arguments in the form
Container.BindInstance(arg).WhenInjectedInto<ResultType>()
InstantiatedCallback = In some cases it is useful to be able customize an object after it is instantiated. In particular, if using a third party library, it might be necessary to change a few fields on one of its types. For these cases you can pass a method to OnInstantiated that can customize the newly created instance. For example:
Container.Bind<Foo>().AsSingle().OnInstantiated<Foo>(OnFooInstantiated); void OnFooInstantiated(InjectContext context, Foo foo) { foo.Qux = "asdf"; }
Or, equivalently:
Container.Bind<Foo>().AsSingle().OnInstantiated<Foo>((ctx, foo) => foo.Bar = "qux");
Note that you can also bind a custom factory using FromFactory that directly calls Container.InstantiateX before customizing it for the same effect, but OnInstantiated can be easier in some cases
Condition = The condition that must be true for this binding to be chosen. See here for more details.
(Copy|Move)Into(All|Direct)SubContainers = This value can be ignored for 99% of users. It can be used to automatically have the binding inherited by subcontainers. For example, if you have a class Foo and you want a unique instance of Foo to be automatically placed in the container and every subcontainer, then you could add the following binding:
Container.Bind<Foo>().AsSingle().CopyIntoAllSubContainers()
In other words, the result will be equivalent to copying and pasting the
Container.Bind<Foo>().AsSingle()
statement into the installer for every sub-container.Or, if you only wanted Foo in the subcontainers and not the current container:
Container.Bind<Foo>().AsSingle().MoveIntoAllSubContainers()
Or, if you only wanted Foo to be in the immediate child subcontainer, and not the subcontainers of these subcontainers:
Container.Bind<Foo>().AsSingle().MoveIntoDirectSubContainers()
NonLazy = Normally, the ResultType is only ever instantiated when the binding is first used (aka "lazily"). However, when NonLazy is used, ResultType will immediately be created on startup.
IfNotBound = When this is added to a binding and there is already a binding with the given contract type + identifier, then this binding will be skipped.