Published: Apr 6, 2025 by mewlist
Unity の初期化順とDoinject を使った初期化順の制御について
この記事では、Unity の初期化順と Doinject を使った初期化順の制御について解説します。
この記事では、Unity 開発における初期化順序の問題に対し、非同期処理をネイティブにサポートする 点が特徴的な DI フレームワーク Doinject を用いた制御方法について解説します。これにより、特に Addressables のような非同期ロードが絡む複雑な初期化シーケンスの管理が、他の多くの DI フレームワークと比較して容易になる可能性があります。
Unity の初期化順序の問題点
Unity 開発において、Awake
や Start
といった MonoBehaviour のライフサイクルメソッドの実行順序は、しばしば開発者を悩ませる問題です。特に、複数のオブジェクトが互いに依存し合っている場合、意図した順序で初期化処理を実行することは困難になることがあります。Script Execution Order を設定する方法もありますが、依存関係が複雑化すると管理が煩雑になりがちです。
Doinject と OnInjected
による解決策
Doinject は、このような初期化順序の問題に対する解決策として OnInjected
コールバックを提供します。これは、Doinject が非同期処理を考慮して設計されている点と関連しています。
特に、Addressables からのプレハブロードなど、非同期処理を伴う依存性注入を行う場合、従来の Awake
や Start
のタイミングでは、依存オブジェクトの準備が完了している保証はありません。インジェクトされるフィールドが null
である可能性があり、従来のライフサイクルメソッドだけでは、この非同期性を考慮した初期化順序の制御は困難でした。
OnInjected
は、この問題に対処するのに役立ちます。このコールバックは、同期的なインジェクションだけでなく、すべての非同期的なインジェクションを含む、DIコンテナによる依存性の注入が完了した後 に呼び出されることが保証されています。
OnInjected
の同期・非同期コールバック
[OnInjected]
でマークされたメソッドは、同期メソッド (void
) または 非同期メソッド (async Task
/ async ValueTask
) として定義できます。
- 同期コールバック (
void
): すべての依存性(同期・非同期問わず)の注入完了後、すぐに実行したい同期的な初期化処理に適しています。 - 非同期コールバック (
async Task
/ValueTask
): すべての依存性注入が完了した後、さらに非同期的な処理(例: ネットワーク通信、追加のリソースロードなど)を実行する必要がある場合に有用です。Doinject は、この非同期コールバックの完了を待機するため、非同期を含む初期化フローを安全に構築できます。
このように、OnInjected
を利用することで、必要な依存オブジェクト(たとえ非同期でロードされるものであっても)がすべて利用可能な状態になってから、同期・非同期を問わず、安全かつ柔軟に初期化処理を実行することができます。
注意: IInjectableComponent
の実装
[Inject]
属性によるインジェクションや [OnInjected]
コールバックを利用するコンポーネントは IInjectableComponent
インターフェースを実装する必要があります。インジェクションもコールバックも不要なコンポーネントには、このインターフェースは必要ありません。
コード例
例えば、あるコンポーネント MyComponent
が、別のコンポーネント DependencyComponent
に依存しているとします。
using UnityEngine;
using Doinject;
// MyComponent は [Inject] と [OnInjected] を使うため IInjectableComponent を実装
public class MyComponent : MonoBehaviour, IInjectableComponent
{
[Inject] private DependencyComponent dependency;
private bool isInitialized = false;
// OnInjected メソッドを非同期 (async Task) として定義する例
[OnInjected]
private async Task InitializeAsync()
{
// この時点では dependency は確実にインジェクトされている
Debug.Log("DependencyComponent is ready. Starting async initialization...");
// 依存コンポーネントを使った同期処理
dependency.DoSomething();
// さらに非同期の初期化処理を実行する例
await Task.Delay(100); // 例: 何らかの非同期待機
Debug.Log("Async initialization part 1 complete.");
await dependency.DoSomethingAsync(); // 依存コンポーネントの非同期メソッド呼び出し
isInitialized = true;
Debug.Log("MyComponent fully initialized asynchronously!");
}
// 同期的な OnInjected メソッドの例 (InitializeAsync とどちらか一方を使用)
/*
[OnInjected]
private void InitializeSync()
{
// この時点では dependency は確実にインジェクトされている
dependency.DoSomething();
isInitialized = true;
Debug.Log("MyComponent initialized synchronously!");
}
*/
void Update()
{
if (!isInitialized) return;
// 初期化後の処理
}
}
// DependencyComponent はインジェクションを受けず、OnInjected も使わないため
// IInjectableComponent は不要だが、非同期メソッドを持つ例
public class DependencyComponent : MonoBehaviour
{
public void DoSomething()
{
Debug.Log("DependencyComponent is doing something synchronously.");
}
public async Task DoSomethingAsync()
{
Debug.Log("DependencyComponent starting async work...");
await Task.Yield(); // 非同期処理のシミュレーション
Debug.Log("DependencyComponent finished async work.");
}
}
上記の例では、MyComponent
は IInjectableComponent
を実装し、InitializeAsync
メソッドが async Task
として定義され [OnInjected]
でマークされています(コメントアウトされた同期版 InitializeSync
も示しています)。Doinject は、dependency
のインジェクション(同期・非同期問わず)が完了した後、マークされた OnInjected
メソッド(この例では InitializeAsync
)を呼び出します。もしメソッドが非同期であれば、Doinject はその完了を待ちます。これにより、Awake
や Start
の実行タイミングや、依存オブジェクトの非同期ロード完了タイミングに悩まされることなく、dependency
が確実に利用可能であることを保証した上で、同期・非同期を含む複雑な初期化フローを実行できます。
まとめ
Doinject の OnInjected
コールバックは、非同期処理を考慮した設計により、初期化制御の有効な手段を提供します。同期・非同期が混在する複雑な依存関係や、Addressables のような非同期ロードが絡むシナリオにおいても、オブジェクト間の初期化順序を明確かつ安全に制御するのに役立ちます。
これにより、Unity 開発における初期化タイミングの問題に対処しやすくなり、コードの可読性と保守性の向上が期待できます。