源代码地址在最后,非常感谢你的观看
1.繁琐的内存管理,在C++时代对内存管理需要new 和 Delete相对应,Delete之后还需要把指针置空,不然会导致内存泄漏或者野指针的问题。
2.市面上普遍的资源管理采用Index计数方式管理,当计数为0的时候就会在内存不足或者特定的地方释放内存比如场景切换。
问题:Index计数方式过于繁琐,需要资源加载和释放完全对应,不然就会有内存泄漏风险。
思考:采用引用计数方式来处理内存释放,当引用数据为Null的时候,释放这个内存。
实践:
环境:Unity 2022.3.62
YooAsset版本:2.3.16
1.点击加载资源,我们可以看到YooAsset的AssrtBundle里面已经有多条加载到内存的数据:

2.我们点击卸载资源,会发现YooAsset的资源已经全部被清空了。

3.原理和核心代码,通过传入GameObject的引用,来保持资源的存活,当这个资源的GameObject的引用不存在的时候,就判定这个资源可以释放了。
cs
public async void LoadAssetAsync<T>(string path,GameObject refObj,Action<T> call)where T : UnityEngine.Object
{
AssetHandle assetHandle = _assetHelper.Package.LoadAssetAsync<T>(path);
await assetHandle.Task;
if (assetHandle.AssetObject == null)
{
Debug.LogError($"资源加载错误LoadAssetAsync:{path}");
return;
}
if (!_autoRefData.TryGetValue(path, out ResAutoRefData resAutoRefData))
{
resAutoRefData = new ResAutoRefData(assetHandle);
_autoRefData.Add(path, resAutoRefData);
}
resAutoRefData.RefObj.Add(refObj);
T asset = assetHandle.AssetObject as T;
call?.Invoke(asset);
}
4.释放资源的代码(可以看到只检测了GameObject被释放的资源):
cs
//释放引用计数为0的资源
public async UniTask UnloadAsset()
{
//因为GameObject销毁是在本帧的最后阶段才会消耗,所以要等待帧结束
await UniTask.WaitForEndOfFrame();
_removeHandle.Clear();
foreach (string path in _autoRefData.Keys)
{
ResAutoRefData resAutoRef = _autoRefData[path];
List<GameObject> refObjs = resAutoRef.RefObj;
int refIndex = 0;
foreach (GameObject refObj in refObjs)
{
if (refObj != null)
{
refIndex++;
break;
}
}
if (refIndex == 0)
{
resAutoRef.Handle.Release();
_removeHandle.Add(path);
}
}
//移除资源的加载
if (_removeHandle.Count != 0)
{
foreach (string path in _removeHandle)
{
_autoRefData.Remove(path);
}
}
UnloadUnusedAssetsOperation unloadUnused = _assetHelper.Package.UnloadUnusedAssetsAsync();
await unloadUnused.Task;
}