Asynchronous DI Container for Unity
以下の順にパッケージをインストールしてください。
https://github.com/mewlist/MewCore.git
https://github.com/mewlist/Doinject.git
Doinject は、Unity 向けの非同期 DI(Dependency Injection) フレームワークです。
非同期DIコンテナというコンセプトが起点となっています。 Unity 2022 LTS / Unity 6 以降をサポートします。
一般的な DI コンテナは登録された型を生成する際、同期的な処理を行います。 しかしながら、Unity で用いられる非同期インスタンス生成機能や、何らかの非同期処理を必要とするインスタンス生成方法には対応できません。
Doinject を導入することで非同期処理によるインスタンスの生成と解放をサポートする DI コンテナを利用することができます。 これにより、Addressables Asset Systems を通した非同期ローディングを伴うインスタンス生成や、非同期IOからロードした情報に基づくインスタンス生成といった、 より柔軟なインスタンス管理を平易な記述で行うことが可能となります。 カスタムファクトリを作成することであらゆる非同期処理を伴うインスタンス生成に対応することができます。
Unity のライフサイクルと矛盾しない形でコンテクスト空間を定義するように設計されています。 シーンを閉じれば、シーンに紐づいたコンテクストを閉じ、そのコンテクスト空間で作成されたインスタンスが消え、 コンテクストを持つゲームオブジェクトを Destroy すれば、同様にコンテクストを閉じます。 コンテクスト空間はフレームワークによって自動的に構成され、複数のコンテクストがロードされた場合には、親子関係を作ります。
Addressable Asset System のインスタンスも扱うことができ、ロードハンドルの解放を自動化することができます。 Addressables のリソース管理は、独自のリソースマネジメントシステムを作ったりと、慎重な実装が必要となりますが、 Doinject を使うと、Addressables のロード・解放を勝手にやってくれます。
ファクトリパターン、(コンテクストに閉じた)シングルトンパターン、サービスロケーターパターンの置き換えを、平易な記述で実現することができます。 また、カスタムファクトリやカスタムリゾルバを作ることで、より複雑なインスタンス生成シナリオに対応することができます。
| 記述 | リゾルバの動作 | 提供タイプ |
|---|---|---|
container.Bind<SomeClass>(); |
new SomeClass() |
cached |
container.Bind<SomeClass>().AsSingleton(); |
new SomeClass() |
singleton |
container.Bind<SomeClass>().AsTransient(); |
new SomeClass() |
transient |
container.Bind<SomeClass>().Args(123,"ABC"); |
new SomeClass(123, "abc") |
cached |
container.Bind<ISomeInterface>().To<SomeClass>(); |
new SomeClass() as ISomeInterface |
cached |
container.Bind<ISomeInterface, SomeClass>(); |
new SomeClass() as ISomeInterface |
cached |
container.Bind<SomeClass>().FromInstance(instance); |
instance |
instance |
container.BindInstance(instance); |
instance |
instance |
提供タイプ(ライフタイム)について:
- cached (デフォルト): コンテナ内で最初に解決されたときにインスタンスが生成され、以降はそのコンテナ内でのリクエストに対して同じインスタンスが再利用されます。コンテナが破棄されるとインスタンスも破棄(
IDisposableやIAsyncDisposableを実装していればDispose/DisposeAsyncが呼ばれる)されます。 - singleton: コンテクストスコープのシングルトン。最初に解決されたコンテクスト内でインスタンスが生成され、そのコンテクストが有効な間、単一のインスタンスが維持されます。コンテクストが破棄されるとインスタンスも破棄されます。親コンテクストで singleton として束縛されたインスタンスは、子コンテクストからも利用できます。
- transient: 解決されるたびに新しいインスタンスが生成されます。インスタンスのライフサイクル管理は Doinject の管理外となります(手動での破棄が必要です)。
- instance: 既存のインスタンスをバインドします。コンテナはそのインスタンスの生成や破棄に関与しません。
| 記述 | リゾルバの動作 |
|---|---|
container.Bind<SomeComponent>(); |
new GameObject().AddComponent<SomeComponent>() |
container.Bind<SomeComponent>().Under(transform); |
var instance = new GameObject().AddComponent<SomeComponent>();instance.transform.SetParent(transform); |
container.Bind<SomeComponent>().On(gameObject); |
gameObject.AddComponent<SomeComponent>() |
container.BindPrefab<SomeComponent>(somePrefab); |
Instantiate(somePrefab).GetComponent<SomeComponent>() |
| 記述 | リゾルバの動作 |
|---|---|
container.BindAssetReference<SomeAddressalbesObject>(assetReference); |
var handle = Addressables.LoadAssetAsync<GameObject>(assetReference)await handle.Task |
container.BindPrefabAssetReference<SomeComponent>(prefabAssetReference); |
var handle = Addressables.LoadAssetAsync<GameObject>(prefabAssetReference)var prefab = await handle.TaskInstantiate(prefab).GetComponent<SomeComponent>() |
container.BindAssetRuntimeKey<SomeAddressalbesObject>("guid or path"); |
var handle = Addressables.LoadAssetAsync<GameObject>("guid or path")await handle.Task |
container.BindPrefabAssetRuntimeKey<SomeComponent>("guid or path"); |
var handle = Addressables.LoadAssetAsync<GameObject>("guid or path")var prefab = await handle.TaskInstantiate(prefab).GetComponent<SomeComponent>() |
| 記述 | インスタンス (仮想コード) |
|---|---|
container.Bind<SomeClass>().AsFactory(); |
var resolver = new TypeResolver<SomeClass>()new Factory<SomeClass>(resolver) as IFactory<SomeClass> |
container.Bind<SomeComponent>().AsFactory(); |
var resolver = new MonoBehaviourResolver<SomeComponent>()new Factory<SomeComponent>(resolver)) as IFactory<SomeComponent> |
container.Bind<SomeClass>().AsCustomFactory<MyFactory>(); |
new CustomFactoryResolver<MyFactory>() as IFactory<SomeClass> |
Adressables と組み合わせることも可能です。 非同期ロードしたプレハブを Instantiate し GetComponent() を行うファクトリを作ることもできます。
container
.BindAssetReference<SomeComponentOnAddressalbesPrefab>(assetReference)
.AsFactory<SomeComponentOnAddressalbesPrefab>();[Inject]
void Construct(IFactory<SomeComponentOnAddressalbesPrefab> factory)
{
var instance = await factory.CreateAsync();
}public class SomeInstaller : BindingInstallerScriptableObject
{
public override void Install(DIContainer container, IContextArg contextArg)
{
container.Bind<SomeClass>();
}
}class ExampleClass
{
// Constructor Injection
public ExampleClass(SomeClass someClass)
{ ... }
}class ExampleClass
{
// Method Injection
[Inject]
public Construct(SomeClass someClass)
{ ... }
}// Inherits IInjectableComponent
class ExampleComponent : MonoBehaviour, IInjectableComponent
{
// Method Injection
[Inject]
public void Construct(SomeClass someClass)
{ ... }
}