什么是对象池?
对象池顾名思义就是存放对象的池子,主要是为了重复利用对象。将不用的对象扔进池子里,需要用的时候再从池子中取出来。这样的一套机制我们称为对象池。
为什么用对象池?
其实从定义我们就可以看出来,说白了就是为了提高资源的复用性。减少频繁的创建和销毁。对程序而言,频繁的创建和销毁就意味着频繁地分配内存和释放内存,这种情况会引起内存抖动、内存碎片、增加GC的负担和触发频率等一些列的性能问题。
对象池的应用场景?
主要作用于频繁创建和销毁的对象,例如子弹
对象池实现
从定义来看,很显然每一种需要频繁创建和销毁的物体都需要实现一套自己对象池,因为在一个对象池中回收和取出的一定是相同的对象,那开发过程中我们可能会遇到很多种不同类型的对象,他们都需要重复利用,那么怎么能实现简单好用的对象池呢?有没有一种方法或模板可以实现所有类型对象的对象池呢?
有经验的开发到这里肯定会想到把对象池的对象给抽象,那么我们可以有一套抽象的对象池来管理抽象的对象,在使用的时候在把对象池和对象进行具象化。就像这样:
public abstract class IPool<T>{}
那下面是不是要想一下,这个抽象的对象池内部需要一些什么样的属性和方法?
首先我们肯定要给这个对象池定一个容量,还要有一个属性来表示当前的存储数量,我们肯定也要定义一个容器来存这些对象,根据对象池的特性我们可以用Queue或者Stack,那这里我选择了Queue。我们还需要一些抽象方法,来创建、回收、销毁对象,也许我们在从对象池中取对象时还需要一些初始化的操作,初始化时也许我们还要接收一些参数,那么这个操作也要抽象成方法。
那大致就是这样:
public abstract class IPool<T>{/// <summary>/// 对象池最大容量/// </summary>public int MaxCount = 20;/// <summary>/// 对象池当前存储数量/// </summary>public int Count => poolQueue == null ? 0 : poolQueue.Count;/// <summary>/// 回收队列/// </summary>private Queue<T> poolQueue = new Queue<T>();/// <summary>/// 创建对象/// </summary>/// <returns></returns>protected abstract T OnCreateItem();/// <summary>/// 获取对象之前(一般用来初始化对象数据)/// </summary>/// <param name="item">对象</param>/// <param name="userData">用户定义数据</param>protected abstract void OnGetBefore(T item, object userData);/// <summary>/// 回收对象时触发/// </summary>/// <param name="item">目标对象</param>/// <returns>是否可以回收</returns>protected abstract bool OnRecycle(T item);/// <summary>/// 销毁对象时触发/// </summary>/// <param name="item">目标对象</param>protected abstract void OnDestroyItem(T item);
}
上面我们基本上就把该抽象的方法都给抽象了,那我们也不能仅仅只是抽象吧好像什么实事也没做,那下面就要写一些看得见摸得着的实际的东西了。
最重要的我们需要取对象和回收对象,回收对象我们可能一次回收一个,也可能一次回收多个,可能这个回收的对象需要重复利用也可能直接舍弃销毁,我们还可能预加载一些对象为了在下一次使用时能快速的拿到
那完整的对象池如下方:
/// <summary>/// 对象池/// </summary>/// <typeparam name="T">池对象泛型</typeparam>public abstract class IPool<T>{/// <summary>/// 对象池最大容量/// </summary>public int MaxCount = 20;/// <summary>/// 对象池当前存储数量/// </summary>public int Count => poolQueue == null ? 0 : poolQueue.Count;/// <summary>/// 回收队列/// </summary>private Queue<T> poolQueue = new Queue<T>();/// <summary>/// 预加载/// </summary>/// <param name="count">预加载数量</param>public void PreLoad(int count){count = Mathf.Min(count, MaxCount);if (Count >= count) return;T item;for (int i = Count; i < count; i++){item = OnCreateItem();Recycle(item);}}/// <summary>/// 取一个对象/// </summary>/// <param name="userData">用户定义数据</param>/// <returns></returns>public T Get(object userData=null){T t;if (Count == 0){t = OnCreateItem();}else{t = poolQueue.Dequeue();if (t == null) t = Get(userData);}OnGetBefore(t,userData);return t;}/// <summary>/// 回收对象/// </summary>/// <param name="item">目标对象</param>/// <param name="isDestroy">是否销毁</param>public void Recycle(T item,bool isDestroy=false){if (Count >= MaxCount) isDestroy = true;if (isDestroy){OnDestroyItem(item);}else{if (OnRecycle(item)){poolQueue.Enqueue(item);}else OnDestroyItem(item);}}/// <summary>/// 回收多个对象/// </summary>/// <param name="items">对象列表</param>/// <param name="isDestroy">是否彻底销毁</param>public void Recycles(IList<T> items,bool isDestroy=false){int count = items == null ? 0 : items.Count;if (count == 0) return;for (int i = 0; i < count; i++){Recycle(items[i],isDestroy);}}/// <summary>/// 创建对象/// </summary>/// <returns></returns>protected abstract T OnCreateItem();/// <summary>/// 获取对象之前(一般用来初始化对象数据)/// </summary>/// <param name="item">对象</param>/// <param name="userData">用户定义数据</param>protected abstract void OnGetBefore(T item, object userData);/// <summary>/// 回收对象时触发/// </summary>/// <param name="item">目标对象</param>/// <returns>是否可以回收</returns>protected abstract bool OnRecycle(T item);/// <summary>/// 销毁对象时触发/// </summary>/// <param name="item">目标对象</param>protected abstract void OnDestroyItem(T item);}
其实到这里就已经写完了,但考虑到有一些同学会问我怎么用???想着尽可能的让你们开箱即食,干脆,咬咬牙,跺跺脚,我再多写200字。
看好了,我要举栗子了…
就以GameObject的对象池为例吧,因为这个的使用频率还是挺高的。
那我们定义一个GameObjectPool来继承IPool,然后再把IPool里的抽象方法都给实现一遍。
喏!,就是这样了:
public class GameObjectPool : IPool<GameObject>{protected GameObject prefab;public GameObjectPool(GameObject prefab){this.prefab = prefab;}protected override GameObject OnCreateItem(){if (prefab==null){Debug.LogError("GameObjectPool 创建对象失败,prefab不能为null");return null;}return GameObject.Instantiate(prefab);}protected override void OnGetBefore(GameObject item, object userData){item.SetActive(true);}protected override bool OnRecycle(GameObject item){item.SetActive(false);return true;}protected override void OnDestroyItem(GameObject item){GameObject.Destroy(item);}}
好了实现了,怎么样 简单吧。。。。
不是吧 ,都到这里了还要问怎么用。。。。
接着来:
public class Demo : MonoBehaviour
{/// <summary>/// cube预制体/// </summary>public GameObject cubePrefab;/// <summary>/// cube 对象池/// </summary>private GameObjectPool cubePool;//在使用的所有cube,方便一会我要回收它们private List<GameObject> cubes = new List<GameObject>();void Start(){//对象池初始化cubePool = new GameObjectPool(cubePrefab);//预加载5个CubecubePool.PreLoad(5);}private void Update(){//点击空格回收所有的cubeif (Input.GetKeyDown(KeyCode.Space)){cubePool.Recycles(cubes);cubes.Clear();}//点击C 创建一个Cubeif (Input.GetKeyDown(KeyCode.C)){GameObject cube = cubePool.Get();cube.transform.position =GetRandomPos();cubes.Add(cube);}}/// <summary>/// 得到一个随机位置/// </summary>/// <returns></returns>private Vector3 GetRandomPos(){return new Vector3(Random.Range(-10, 10), Random.Range(-10, 10), Random.Range(-10, 10));}}
基本上就是这样了,其它对象的对象池也像GameObjectPool的实现方式,继承IPool 实现所有的抽象方法就可以了。
好了 结束!