Quick Flow
| 요청 | 결과 | release 방식 | 소유권 |
|---|---|---|---|
LoadAssetAsync<T> | asset handle | Addressables.Release(handle) | 마지막 asset 사용자가 handle을 한 번 release |
InstantiateAsync | instance handle | Addressables.ReleaseInstance(handle) | 만든 instance의 owner가 destroy와 release를 함께 처리 |
LoadAssetsAsync | 여러 asset handle | Addressables.Release(handle) | batch 결과와 dependency를 요청 단위로 해제 |
| 실패한 load | Status == Failed, OperationException | 여전히 release | 실패한 operation도 caller가 얻은 handle입니다. |
AsyncOperationHandle은 결과만 담는 값이 아니라 operation과 dependency reference의 소유 증표입니다. 완료된 뒤 Status를 확인하고, caller가 만든 handle은 성공·실패·화면 닫힘 경로를 포함해 정확히 한 번 release합니다.
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
private AsyncOperationHandle<GameObject> _prefabHandle;
private bool _ownsPrefabHandle;
private async void LoadPrefab()
{
_prefabHandle = Addressables.LoadAssetAsync<GameObject>(enemyKey);
_ownsPrefabHandle = true;
await _prefabHandle.Task;
if (_prefabHandle.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError(_prefabHandle.OperationException);
ReleasePrefab();
return;
}
_instance = Instantiate(_prefabHandle.Result);
}
private void OnDestroy() => ReleasePrefab();
private void ReleasePrefab()
{
if (!_ownsPrefabHandle) return;
if (_prefabHandle.IsValid()) Addressables.Release(_prefabHandle);
_ownsPrefabHandle = false;
}어떤 것을 생성했는가
LoadAssetAsync<T>는 asset과 dependency를 가져오지만 GameObject instance를 만들지는 않습니다. 같은 prefab을 여러 번 Unity Instantiate로 생성하려면 original asset handle과 clone 수명의 관계를 caller가 관리해야 합니다. asset handle을 너무 이르게 release하면 clone이 아직 참조하는 dependency가 unload될 수 있습니다.
InstantiateAsync는 instance마다 operation handle을 돌려줍니다. independently owned prefab을 만들고 제거하는 흐름이면 이 방식이 소유권을 드러내기 쉽습니다. Destroy(instance)만 호출하는 대신 ReleaseInstance를 호출해 Addressables reference까지 반환합니다.
private AsyncOperationHandle<GameObject> _spawnHandle;
private async void Spawn()
{
_spawnHandle = Addressables.InstantiateAsync(enemyReference);
await _spawnHandle.Task;
if (_spawnHandle.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError(_spawnHandle.OperationException);
Addressables.Release(_spawnHandle);
return;
}
}
private void Despawn()
{
if (_spawnHandle.IsValid()) Addressables.ReleaseInstance(_spawnHandle);
}완료, 실패, 취소처럼 보이는 화면 전환
await/coroutine/event 중 어떤 대기 방식을 쓰더라도 Status, Result, OperationException의 확인 지점은 같습니다. 화면이 닫혀 더 이상 결과가 필요 없어졌다면 operation을 취소했다고 가정하지 말고, handle release 정책을 적용합니다. 로드가 completion race에서 끝날 수 있으므로 completion handler가 destroyed UI를 다시 갱신하지 않도록 owner의 활성 상태도 확인합니다.
shared dependency는 여러 load handle이 참조할 수 있습니다. 한 consumer가 release했다고 즉시 unload되는 것이 아니라, 모든 관련 reference가 반환된 뒤에야 해제될 수 있습니다. 반대로 같은 handle을 두 번 release하면 다른 consumer의 reference count를 대신 줄이는 오류가 됩니다.
자주 틀리는 부분
| 증상 | 원인 | 수정 방향 |
|---|---|---|
| 실패 뒤에도 memory가 남음 | failed handle을 release하지 않았습니다. | 실패 경로도 success와 같은 ownership table에 넣습니다. |
| prefab clone이 나중에 깨짐 | asset handle을 clone보다 먼저 release했습니다. | clone 수명 동안 asset/dependency owner를 유지하거나 InstantiateAsync를 씁니다. |
| scene 전환 뒤 인스턴스가 남음 | Destroy와 ReleaseInstance 책임이 분리됐습니다. | instance owner가 둘을 한 메서드에서 처리합니다. |
| 같은 handle을 두 번 release | OnDestroy와 실패 경로가 겹쳤습니다. | IsValid와 explicit owner flag로 한 번만 반환합니다. |
| 로드 완료 후 닫힌 UI가 다시 열림 | completion callback이 화면 수명을 확인하지 않습니다. | result 적용 전 owner active/identity를 확인합니다. |
Addressables의 release는 가비지 컬렉션을 강제로 실행하는 명령이 아닙니다. operation reference를 반환하는 행위입니다. asset, dependency, instance가 어떤 handle에서 생겼고 누가 마지막 release를 하는지 추적할 수 있어야 메모리 문제를 실제로 고칠 수 있습니다.
참고 링크
3 sources