标签:style blog http color io 使用 strong ar for
对象池背后的理念其实是非常简单的。我们将对象存储在一个池子中,当需要时在再次使用,而不是每次都实例化一个新的对象。池的最重要的特性,也就是对象池设计模式的本质是允许我们获取一个“新的”对象而不管它真的是一个新的对象还是循环使用的对象。该模式可以用以下简单的几行代码实现:
public class ObjectPool<T> where T : class, new() { private Stack<T> m_objectStack = new Stack<T>(); public T New() { return (m_objectStack.Count == 0) ? new T() : m_objectStack.Pop(); } public void Store(T t) { m_objectStack.Push(t); } }
很简单,也很好地体现了该模式的核心。如果你不太理解”where T”,没关系,稍后会解释的。如何使用呢?很简单,你只需要找到之前使用new操作符的表达式,例如:
void Update() { MyClass m = new MyClass(); }
然后将其替换为New()和Store()。
ObjectPool<MyClass> poolOfMyClass = new ObjectPool<MyClass>(); void Update() { MyClass m = poolOfMyClass.New(); // do stuff... poolOfMyClass.Store(m); }
我是简洁主义的忠实信徒,但就目前而言ObjectPool类或许过于简单了。如果你搜索下用C#实现的对象池类库,你会发现其中很多是相当复杂的。我们先暂停一下,仔细想想在一个通用的对象池中到底哪些是我们需要的,哪些是不需要的:
那么其中那些是必需的呢?你的答案或许和我的不一样,但请允许我阐述我的观点:
修订后的版本如下:
public class ObjectPool<T> where T : class, new() { private Stack<T> m_objectStack; private Action<T> m_resetAction; private Action<T> m_onetimeInitAction; public ObjectPool(int initialBufferSize, Action<T> ResetAction = null, Action<T> OnetimeInitAction = null) { m_objectStack = new Stack<T>(initialBufferSize); m_resetAction = ResetAction; m_onetimeInitAction = OnetimeInitAction; } public T New() { if (m_objectStack.Count > 0) { T t = m_objectStack.Pop(); if (m_resetAction != null) m_resetAction(t); return t; } else { T t = new T(); if (m_onetimeInitAction != null) m_onetimeInitAction(t); return t; } } public void Store(T obj) { m_objectStack.Push(obj); } }
该实现非常简单直白。参数T被指明为”where T:class,new()”,意味着有两个限制。首先,T必须为一个类(毕竟,只有引用类型需要被obejct-pool);其次,它必须要有一个无参构造函数。
构造函数将池可能的最大值作为第一个参数。另外两个是可选的闭包,如果传入值,第一个闭包将用来重置池,第二个初始化一个新的对象。除了构造函数外,ObjectPool<T>只有两个方法:New()和Store()。因为池使用了延迟策略,主要的工作在于New()。其中,新的和循环使用的对象要么被实例化,要么被重置,这两个操作通过传入的闭包实现。以下是池的使用方法:
class SomeClass : MonoBehaviour { private ObjectPool<List<Vector3>> m_poolOfListOfVector3 = //32为假设的最大数量 new ObjectPool<List<Vector3>>(32, (list) => { list.Clear(); }, (list) => { //初始化容量为1024 list.Capacity = 1024; }); void Update() { List<Vector3> listVector3 = m_poolOfListOfVector3.New(); // do stuff m_poolOfListOfVector3.Store(listVector3); } }
上述的对象池实现了基本功能,但还是有瑕疵。它将初始化和重置对象在对象定义中分开了,在一定程度了违反了封装原则。导致了紧耦合,这是需要尽可能避免的。在上述SomeClass中,我们是没有真正的替代方案的,因为我们不能修改List<T>的定义。然而,当你用自定义类时,你可以实现IResetable接口作为代替。对应的ObjectPoolWithReset<T>也可以不需要指明两个闭包了(请注意,为了灵活性我还是留下了)。
public interface IResetable { void Reset(); } public class ObjectPoolWithReset<T> where T : class, IResetable, new() { private Stack<T> m_objectStack; private Action<T> m_resetAction; private Action<T> m_onetimeInitAction; public ObjectPoolWithReset(int initialBufferSize, Action<T> ResetAction = null, Action<T> OnetimeInitAction = null) { m_objectStack = new Stack<T>(initialBufferSize); m_resetAction = ResetAction; m_onetimeInitAction = OnetimeInitAction; } public T New() { if (m_objectStack.Count > 0) { T t = m_objectStack.Pop(); //自行重置 t.Reset(); if (m_resetAction != null) m_resetAction(t); return t; } else { T t = new T(); if (m_onetimeInitAction != null) m_onetimeInitAction(t); return t; } } public void Store(T obj) { m_objectStack.Push(obj); } }
有一些类型不需要在一系列帧中存留,仅在帧结束前就失效了。在这种情况下,我们可以在一个合适的时机将所有已经池化的对象(pooled objects)再次存储于池中。现在,我们重写该池使之更加简单高效。
public class ObjectPoolWithCollectiveReset<T> where T : class, new() { private List<T> m_objectList; private int m_nextAvailableIndex = 0; private Action<T> m_resetAction; private Action<T> m_onetimeInitAction; public ObjectPoolWithCollectiveReset(int initialBufferSize, Action<T> ResetAction = null, Action<T> OnetimeInitAction = null) { m_objectList = new List<T>(initialBufferSize); m_resetAction = ResetAction; m_onetimeInitAction = OnetimeInitAction; } public T New() { if (m_nextAvailableIndex < m_objectList.Count) { // an allocated object is already available; just reset it T t = m_objectList[m_nextAvailableIndex]; m_nextAvailableIndex++; if (m_resetAction != null) m_resetAction(t); return t; } else { // no allocated object is available T t = new T(); m_objectList.Add(t); m_nextAvailableIndex++; if (m_onetimeInitAction != null) m_onetimeInitAction(t); return t; } } public void ResetAll() { //重置索引 m_nextAvailableIndex = 0; } }
相比于原始的ObjectPool<T>,改动还是蛮大的。先不管类的签名,可以看到,Store()已经被ResetAll()代替了,且仅在所有已经分配的对象需要被放入池中时调用一次。在类内部,Stack<T>被List<T>代替,其中保存了所有已分配的对象(包括正在使用的对象)的引用。我们也可以跟踪最近创建或释放的对象在list中索引,由此,New()便可以知道是创建一个新的对象还是重置一个已有的对象。
上文讲解了ObjectPool的基本原理及其实现。下面推荐一个更加成熟的插件——PoolManager,该插件功能十分强大,所以才敢卖那么贵,30美刀...国内有位牛人已经写了一份不错的教程,有兴趣的童鞋可以参考下:Unity3D研究院之初探PoolManager插件。
[译]Unity3D内存管理——对象池(Object Pool)
标签:style blog http color io 使用 strong ar for
原文地址:http://www.cnblogs.com/mezero/p/3955130.html