UITableView DequeueReusableCell复用异常:重复创建单元且旧单元未释放
针对你遇到的高可变高度Cell(40-1000px)复用率不足、旧实例无法释放且内存占用高的问题,我结合SkiaSharp的使用场景给你几个具体的排查和解决方向:
先搞定复用率低的核心问题
首先检查Cell的注册与复用逻辑是否规范:确保你已经通过RegisterClassForCellReuse或RegisterNibForCellReuse完成了Cell的注册,且DequeueReusableCell时传入了正确的identifier——不要手动写if (cell == null) { new MyCell() }这种兜底逻辑,注册后的dequeue应该能稳定返回复用实例。
另外,高度预估不准是导致系统频繁创建新Cell的常见原因:如果你的Cell用自动布局,一定要确保约束完整可解析;如果是手动计算高度,建议提前缓存每个indexPath对应的高度(比如用字典存储),并在GetHeightForRow中直接返回缓存值,同时合理设置EstimatedRowHeight——系统如果无法准确预估高度,会误以为现有复用Cell的尺寸不匹配当前行,从而创建新实例。强制清理SkiaSharp的非托管资源
SKCanvasView依赖底层图形API(OpenGL/Metal),其相关对象(SKBitmap、SKPaint、SKCanvas等)都是非托管资源,.NET GC不会自动回收,必须手动释放:
在Cell的PrepareForReuse方法里,一定要做这些操作:public override void PrepareForReuse() { base.PrepareForReuse(); // 解除PaintSurface事件绑定,避免循环引用 _canvasView.PaintSurface -= OnCanvasPaintSurface; // 释放自定义的SkiaSharp资源 _myBitmap?.Dispose(); _myPaint?.Dispose(); _myBitmap = null; _myPaint = null; // 重置画布 _canvasView.InvalidateSurface(); }另外,当Cell即将从TableView中移除时(比如在
WillMoveToSuperview方法中判断superview == null),也要重复一遍上述清理操作,确保资源被彻底释放。排查强引用导致的内存泄漏
你提到用了WeakReference但没解决问题,大概率是存在未被发现的强引用链:- 检查Cell是否订阅了外部对象(比如ViewModel、全局事件)的事件,且没有在合适时机解除订阅——这些订阅会形成强引用,把Cell牢牢拉住。
- 用内存分析工具(比如Xcode Instruments的Leaks,或者Visual Studio for Mac的内存探查器)跟踪Cell实例的引用链,找到持有Cell强引用的对象,针对性解除。
优化复用池的适配逻辑
由于你的Cell高度跨度极大(40到1000px),系统默认的复用池大小计算可能不准确,导致滚动时临时创建新Cell。你可以尝试:- 手动设置
TableView.PreferredMaxLayoutWidth为你Cell的最大宽度,帮助系统更准确计算所需的复用实例数量。 - 如果TableView的滚动方向是垂直,确保你的Cell的高度计算逻辑没有依赖动态变化的外部因素,避免系统频繁重新计算高度而放弃复用现有Cell。
- 手动设置
按照这个思路一步步排查,应该能解决复用率低和内存泄漏的问题。
内容的提问来源于stack exchange,提问作者Michal Dobrodenka

