Unity MyFramework 用法说明(十):使用 ClassObject 和 ClassScope 管理可复用对象

📅 2026/7/18 22:18:44
Unity MyFramework 用法说明(十):使用 ClassObject 和 ClassScope 管理可复用对象
上一篇介绍 EventSystem 时带参数事件通过ClassScope创建using var a new ClassScopeEventLocationChanged(out var param);ClassScope背后使用的是 MyFramework 的ClassPool。它不仅能减少高频创建临时对象产生的 GC也为普通 C# 对象提供了一套统一的创建、重置和销毁流程。项目地址https://github.com/ZHOURUIH/MyFramework一、为什么需要 ClassPool游戏运行过程中会频繁创建各种临时对象例如事件参数 技能参数 子弹参数 网络消息 状态机参数 伤害数字数据 临时查询结果直接使用new创建这些对象EventLocationChanged param new EventLocationChanged();使用结束后只能等待 GC 回收。当创建频率较高时大量短生命周期对象会持续产生托管内存分配最终引起 GC。MyFramework 的处理方式是从 ClassPool 获取对象 ↓ 使用对象 ↓ 将对象归还 ClassPool ↓ 下次继续复用要加入这套对象池体系类需要继承ClassObject。二、创建一个 ClassObject以项目中的拍卖物品数据为例public class AuctionItem : ClassObject { protected long mInstanceID; protected int mTimeMinute; protected int mPrice; protected int mAveragePrice; public void setInstanceID(long instanceID) { mInstanceID instanceID; } public void setTime(int timeMinute) { mTimeMinute timeMinute; } public void setPrice(int price) { mPrice price; } public void setAveragePrice(int price) { mAveragePrice price; } public long getInstanceID() { return mInstanceID; } public int getTime() { return mTimeMinute; } public int getPrice() { return mPrice; } public int getAveragePrice() { return mAveragePrice; } public override void resetProperty() { base.resetProperty(); mInstanceID 0; mTimeMinute 0; mPrice 0; mAveragePrice 0; } }resetProperty()非常重要。对象归还对象池时框架会调用它清理数据。下次重新取出同一个实例时不应该保留上一次使用的状态。所以继承ClassObject后所有字段都应该在这里重置public override void resetProperty() { base.resetProperty(); mTarget null; mCount 0; mPosition Vector3.zero; mFinished false; }遗漏任何字段都可能导致复用对象时读取到旧数据。三、使用 CLASS 创建长期对象通过CLASST()从对象池获取对象using static FrameUtility; AuctionItem item CLASSAuctionItem(); item.setInstanceID(instanceID); item.setTime(timeMinute); item.setPrice(price); item.setAveragePrice(averagePrice);实际项目中的AuctionSystem会把拍卖物品长期保存在字典中using System.Collections.Generic; using static FrameUtility; public class AuctionSystem : FrameSystem { protected Dictionarylong, AuctionItem mAuctionList new(); public AuctionItem createAuctionItem( long instanceID, int timeMinute, int price, int averagePrice) { if (!mAuctionList.getOrAddClass( instanceID, out AuctionItem item)) { item.setInstanceID(instanceID); item.setTime(timeMinute); item.setPrice(price); item.setAveragePrice(averagePrice); } return item; } }CLASST()创建的是需要手动管理生命周期的对象。只要业务仍然持有这个对象它就不会自动归还对象池。四、使用 UN_CLASS 回收对象拍卖物品失效后通过UN_CLASS回收public void destroyAuction(long instanceID) { mAuctionList.Remove( instanceID, out AuctionItem auction); UN_CLASS(ref auction); }推荐传入refUN_CLASS(ref auction);回收完成后框架会同时把当前变量设置为null减少继续误用该引用的可能性。对象回收过程大致为标记对象正在销毁 ↓ 调用 destroy() ↓ 调用 resetProperty() ↓ 放回未使用队列下一次调用CLASSAuctionItem()时就可以直接取出这个对象继续使用。五、回收对象集合如果一个列表中的元素全部来自ClassPool可以统一回收protected ListAuctionItem mAuctionList new(); public override void destroy() { UN_CLASS_LIST(mAuctionList); base.destroy(); }UN_CLASS_LIST会逐个回收列表中的 ClassObject 清空列表它支持常见集合ListT HashSetT DictionaryTKey, TValue QueueT例如销毁全部子弹public void destroyAllBullet() { UN_CLASS_LIST(mBulletList.getMainList()); mBulletList.clear(); }注意集合本身不会因此变成null只是其中的对象被回收并清空。六、使用 ClassScope 创建临时对象有些对象只在当前代码块中临时使用例如事件参数public class EventLocationChanged : GameEvent { public Vector2Int mLastTilePosition; public Vector2Int mTilePosition; public override void resetProperty() { base.resetProperty(); mLastTilePosition Vector2Int.zero; mTilePosition Vector2Int.zero; } }发送事件时使用ClassScopeusing var a new ClassScopeEventLocationChanged( out var param); param.mLastTilePosition lastTilePosition; param.mTilePosition newTilePosition; mEventSystem.pushEvent(param);离开当前作用域时ClassScope.Dispose()会自动将对象归还对象池。完整流程是进入作用域 ↓ 从 ClassPool 获取 EventLocationChanged ↓ 填写事件参数并发送 ↓ 离开作用域 ↓ 自动归还对象池不需要再手动调用UN_CLASS(ref param);对于明确只在当前作用域使用的临时对象优先使用ClassScope可以避免忘记回收。七、不能保存 ClassScope 中的对象下面的写法是错误的protected EventLocationChanged mLastEvent; public void sendEvent() { using var a new ClassScopeEventLocationChanged(out var param); mLastEvent param; mEventSystem.pushEvent(param); }离开sendEvent()后param已经被归还对象池。但mLastEvent仍然保存着它的引用。这个实例随后可能被其他逻辑重新取出并修改。因此ClassScope中的对象只能在当前作用域内使用可以立即读取 可以传给同步执行的函数 不能保存到成员变量 不能放进长期存在的集合 不能在作用域结束后继续访问如果对象需要长期保存应使用CLASST()创建并在生命周期结束时通过UN_CLASS回收。八、ClassObject 的生命周期ClassObject主要包含三个生命周期函数public virtual void onCreate() { } public virtual void destroy() { } public virtual void resetProperty() { base.resetProperty(); }它们的职责不同。onCreate每次对象从池中分配出来时调用public override void onCreate() { base.onCreate(); // 每次开始使用对象时执行 }无论对象是第一次创建还是从池中复用都会执行。destroy对象归还对象池前调用public override void destroy() { // 注销事件、解除外部关联等 base.destroy(); }适合处理具有行为意义的清理例如取消事件监听 解除管理器引用 移出其他容器 释放关联资源resetProperty负责将所有字段恢复到默认状态public override void resetProperty() { base.resetProperty(); mOwner null; mID 0; mData null; }它不负责业务逻辑只负责保证对象下次使用时是干净的。九、不要依赖构造函数重复初始化对象池复用时不会再次调用构造函数。下面的写法只能在对象第一次被new出来时执行public AuctionItem() { mTimeMinute 10; }对象回收后重新获取不会再次进入构造函数。需要每次分配时执行的逻辑应放在public override void onCreate() { base.onCreate(); }需要清理的字段应放在public override void resetProperty() { base.resetProperty(); }ClassPool 也不支持依赖带参数构造函数创建对象因为对象复用时无法重新传入构造参数。十、主线程和子线程对象池普通接口CLASST(); UN_CLASS(ref obj);使用的是主线程ClassPool不能在子线程中调用。子线程需要使用MyData data CLASS_THREADMyData(); UN_CLASS_THREAD(ref data);主线程对象池和线程对象池彼此独立不应该混合创建和回收。也就是CLASS 创建的对象 必须由 UN_CLASS 回收 CLASS_THREAD 创建的对象 必须由 UN_CLASS_THREAD 回收十一、CLASS 和 ClassScope 如何选择长期保存的业务对象AuctionItem item CLASSAuctionItem(); mAuctionList.Add(item.getInstanceID(), item);生命周期结束时手动回收UN_CLASS(ref item);只在当前代码块使用的临时对象using var a new ClassScopeEventLocationChanged( out var param);选择规则可以概括为需要跨函数或长期保存 使用 CLASS UN_CLASS 只在当前作用域临时使用 使用 ClassScope十二、总结MyFramework 的 ClassPool 用法主要包括1. 可复用对象继承 ClassObject 2. 在 resetProperty 中重置全部字段 3. 使用 CLASS 获取长期对象 4. 使用 UN_CLASS 手动回收 5. 使用 UN_CLASS_LIST 批量回收 6. 使用 ClassScope 管理临时对象 7. 不要保存已经离开作用域的临时对象 8. 子线程使用 CLASS_THREAD对象池不只是把new换成CLASS。真正重要的是为普通对象建立清晰的生命周期创建或复用 ↓ 初始化并使用 ↓ 销毁业务关联 ↓ 重置全部状态 ↓ 等待下一次复用只有正确实现创建、使用和回收流程对象复用才能在减少 GC 的同时保持数据安全。