[Unity] Addressable과 메모리 관리
Addressable을 이용하고 있다면 메모리 관리 측면에서 잘 사용하고 있는지 체크해 볼 필요가 있다.
public IEnumerator Start() {
opHandle = Addressables.LoadAssetAsync<GameObject>(key);
yield return opHandle;
if (opHandle.Status == AsyncOperationStatus.Succeeded) {
GameObject obj = opHandle.Result;
Instantiate(obj, transform);
}
}
//https://docs.unity3d.com/Packages/com.unity.addressables@1.18/manual/LoadingAddressableAssets.html
LoadAssetAsync를 통해 해당 키를 가진 번들을 메모리에 올리고, Instantiate를 통해 오브젝트를 생성하는 루틴은 아무 문제가 없다. 더불어 OnDestory일 때,
void OnDestroy() {
Addressables.Release(opHandle);
}
LoadAssetAsync에서 저장해둔 핸들을 통해 번들을 릴리즈함으로써 메모리에서 해제할 수 있다. (정확히는 Garbage Collect전까지 메모리에 상주한다.)
하지만, 만약 opHandle을 저장하고 있고, 여러 클래스에서 같은 번들내에 다른 오브젝트를 instantiate한다면, 어느 타이밍에 Addressable.Release를 해야 할지 정하는 것은 어렵거나 관리 비용이 많이 드는 일이다. 그렇다고 매번 각각의 오브젝트들을 위와 같은 패턴으로 만든다면, 같은 번들을 반복해서 메모리에 올리고 내리기 때문에 불필요한 CPU 비용을 낭비하는 결과로 이어진다.
아예 Addressables.Release를 사용 안하기도 한다. 이럴 경우 한번 로드한 번들은 프로그램 종료시까지 메모리에서 상주하기 때문에 동작에는 이상이 없지만, 점차 컨텐츠가 늘어날 수록 메모리를 차지하는 비중도 더 늘어나게 된다. 그렇기 때문에, Addressables.Release하는 것은 반드시 필요한 일이다.
그럼 어떻게 번들을 로드하고, 생성한 오브젝트들의 수명이 다 한 후에 그 번들을 메모리에서 사라지게 만들 수 있을까.
Addressables.InstantiateAsync를 사용하면 위의 고민들을 해결 할 수 있다. 이때 InstantiateAsync 인자 중 trackHandle을 true로 해야 한다.
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class InstantiateFromKey : MonoBehaviour
{
public string key; // Identify the asset
void Start() {
// Load and instantiate
Addressables.InstantiateAsync(key).Completed += instantiate_Completed;
}
private void instantiate_Completed(AsyncOperationHandle<GameObject> obj) {
// Add component to release asset in GameObject OnDestroy event
obj.Result.AddComponent(typeof(SelfCleanup));
}
}
// Releases asset (trackHandle must be true in InstantiateAsync,
// which is the default)
public class SelfCleanup : MonoBehaviour
{
void OnDestroy() {
Addressables.ReleaseInstance(gameObject);
}
}
//https://docs.unity3d.com/Packages/com.unity.addressables@1.18/manual/LoadingAddressableAssets.html
InstantiateAsync는 key를 포함한 번들을 메모리에 로드하고, 그 key로 된 오브젝트를 생성한다. 비동기 함수이고, trackHandle 인자가 true일 경우, 레퍼런스 카운팅을 통해 생성된 오브젝트들을 트레킹한다. 생성된 오브젝트들이 모두 소멸할 경우 레퍼런스 카운트가 0이 되어 메모리에 로드된 번들이 자동으로 해제된다. 위의 예처럼 레퍼런스 카운팅을 위해 SelfCleanup.cs를 별도로 만들어두어 동적으로 AddComponent하도록 하고, Addressables.ReleaseInstance가 호출되도록 구조를 만들면 된다.
레퍼런스 카운팅을 실시간으로 확인하기 위해서는 Addressables 메뉴에 Event Viewer을 통해 확인 가능하다.
Event Viewer에서는 현재 로드되어 있는 오브젝트/번들/레퍼런스 카운트를 확인할 수 있다.