自定义UIView作为TableView Footer时的内存泄漏问题求助
嘿,我帮你分析下这个自定义TableFooterView导致的内存问题,应该能解决你的困扰!
首先,从你的描述和代码来看,核心问题是VC销毁后自定义FooterView没有被正确释放,导致内存无法回收。我整理了几个可能的原因和对应的解决方案:
一、先定位泄漏根源
首先建议你用Xcode的Memory Graph Debugger(调试工具栏里的内存图标)来精准定位问题:在VC销毁后,搜索MessageTableFooterView,查看它的引用链——到底是哪个对象还在强引用它,这是最直接找到泄漏点的方法。
常见的可疑点:
- 有没有不小心把FooterView的引用存在了单例(比如你的
MessageService.instance)里? - UITextView虽然你没设置delegate,但有没有其他隐式的强引用(比如布局代码或第三方库)?
二、优化FooterView的创建与内存管理
你的代码里每次调用viewForFooterInSection都会创建新的FooterView,不仅会导致内存波动,还可能增加泄漏风险。另外,初始化代码里的子视图处理也有可以优化的地方:
1. 复用FooterView(和Cell一样)
给FooterView注册复用标识符,避免重复创建实例:
// 在VC的viewDidLoad里注册FooterView override func viewDidLoad() { super.viewDidLoad() tableView.register(MessageTableFooterView.self, forHeaderFooterViewReuseIdentifier: "MessageFooter") } // 修改viewForFooterInSection方法 func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { guard MessageService.instance.savedMsgs.count == 0 else { return nil } let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "MessageFooter") as! MessageTableFooterView footerView.logoView.image = UIImage(named: "savedMessagesIcon") footerView.explanationTextView.text = "Blablabla" return footerView }
2. 用懒加载优化FooterView的子视图
把强引用的子视图改成懒加载,既符合Swift的最佳实践,也能避免提前初始化带来的内存浪费:
class MessageTableFooterView: UIView { // 懒加载Logo视图 lazy var logoView: UIImageView = { let imageView = UIImageView(frame: .zero) imageView.contentMode = .scaleAspectFit // 避免图片拉伸导致额外内存开销 imageView.autoSetDimension(.height, toSize: 145) imageView.autoSetDimension(.width, toSize: 145) return imageView }() // 懒加载文本视图 lazy var explanationTextView: UITextView = { let textView = UITextView(frame: .zero) textView.isEditable = false textView.isSelectable = false textView.font = UIFont(name: "HelveticaNeue", size: 17) textView.textColor = .darkGray textView.textAlignment = .center textView.backgroundColor = .clear // 去掉默认背景,减少绘制开销 return textView }() // 补全按钮的初始化(你之前只声明了没初始化添加) lazy var addFriendBtn: UIButton = { let btn = UIButton(type: .system) btn.setTitle("Add Friend", for: .normal) // 这里可以添加按钮的其他配置 return btn }() override init(frame: CGRect){ super.init(frame: frame) setupSubviewsAndConstraints() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupSubviewsAndConstraints() } // 统一处理子视图添加和约束 private func setupSubviewsAndConstraints() { addSubview(logoView) addSubview(explanationTextView) addSubview(addFriendBtn) // 给所有子视图设置完整约束(你之前只给logo设了尺寸,没设位置) logoView.autoCenterInSuperview() explanationTextView.autoPinEdge(.top, to: .bottom, of: logoView, withOffset: 16) explanationTextView.autoPinEdge(toSuperviewEdge: .left, withInset: 16) explanationTextView.autoPinEdge(toSuperviewEdge: .right, withInset: 16) explanationTextView.autoSetDimension(.height, toSize: 80) // 固定高度,避免滚动内存开销 addFriendBtn.autoPinEdge(.top, to: .bottom, of: explanationTextView, withOffset: 16) addFriendBtn.autoAlignAxis(toSuperviewAxis: .vertical) addFriendBtn.autoSetDimension(.height, toSize: 44) addFriendBtn.autoSetDimension(.width, toSize: 200) } }
三、处理UIImage的缓存问题
UIImage(named:)会把图片缓存到系统内存中,即使VC释放,缓存也不会立即清除,这可能让你误以为内存没释放。如果要避免这个问题,可以改用UIImage(contentsOfFile:)加载图片,这样图片不会被系统缓存:
if let imagePath = Bundle.main.path(forResource: "savedMessagesIcon", ofType: "png") { footerView.logoView.image = UIImage(contentsOfFile: imagePath) }
四、检查单例MessageService的内存管理
确认你的MessageService.instance单例实现是正确的,有没有不小心强引用了VC或者FooterView:
class MessageService { static let instance = MessageService() private init() {} // 私有初始化,避免外部创建 var savedMsgs: [Any] = [] // 其他代码... }
如果单例里有闭包、代理或者其他持有VC的强引用,会导致VC无法释放,进而FooterView也无法回收。
最后验证
修改完后,再用Memory Graph Debugger检查VC销毁后,MessageTableFooterView是否被正确释放。如果还有问题,看引用链里的持有对象,针对性解决就行。
内容的提问来源于stack exchange,提问作者Franc

