UIKit子控制器循环引用问题:添加后内存无法释放
子控制器无法释放的内存泄漏排查与解决
我开发App时,将类似苹果Settings应用的UITableView封装为ChildController,用于在多个主控制器中复用表格布局与功能,但添加子控制器后,该实例始终被内存持有无法释放。
相关代码片段
子控制器移除逻辑
guard parent != nil else { return } willMove(toParent: nil) removeFromParent() view.removeFromSuperview()
主控制器添加子控制器逻辑
addChild(controller) controller.view.frame = self.view.frame view.addSubview(controller.view) controller.didMove(toParent: self)
子控制器核心代码
class ChildController: UIViewController { //MARK: - Propeties public var sections: [FormSection_Model] = [] { didSet { tableView.reloadData() } } public private(set) lazy var tableView: UITableView = { return UITableView(frame: .zero, style: style) }() override func setUp() { super.setUp() hideKeyboardWhenTappedAround() tableView.dataSource = self tableView.delegate = self tableView.register(ButtonForm_Cell.self) tableView.register(SwitchForm_Cell.self) tableView.register(TextFieldForm_Cell.self) tableView.register(TextViewForm_Cell.self) tableView.register(PickerForm_Cell.self) tableView.register(MultiPickerForm_Cell.self) tableView.register(LinkForm_Cell.self) tableView.contentInsetAdjustmentBehavior = .never } override func setUpLayout() { super.setUpLayout() view.addSubview(tableView) tableView.frame = view.frame tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 150, right: 0) } } extension ChildController: UITableViewDelegate, UITableViewDataSource { //TableView delegate funcs... }
主控制器核心代码
class Controller: UIViewController { public private(set) lazy var formController: ChildController = { let controller = FormTableView_Controller(style: .insetGrouped) return controller }() override func setUp() { super.setUp() addChildController(formController) } override func cleanContent() { super.cleanContent() formController.removeFromParentViewController() } private func setSections() { formController.sections.append([ //add items to child controller ]) } }
排查与解决方案
1. 主控制器对子控制器的强引用未释放
主控制器中formController是强引用的lazy属性,即便移除了父子控制器关系,主控制器仍持有该实例的强引用,导致无法回收。
解决步骤:
- 修改
formController的属性定义,允许置空:public var formController: ChildController? = { let controller = FormTableView_Controller(style: .insetGrouped) return controller }() - 在清理方法中移除子控制器后,将引用置空:
override func cleanContent() { super.cleanContent() formController?.removeFromParentViewController() formController = nil // 释放强引用 }
2. 检查子控制器内部的循环引用
- TableView代理/数据源:确保自定义Cell的回调闭包使用
[weak self]捕获控制器,避免Cell持有控制器强引用:// 示例:Cell回调处理 cell.onAction = { [weak self] in self?.handleCellAction() } - Sections模型闭包:若
FormSection_Model或其内部Item包含闭包,需使用weak/unowned捕获子控制器:// 示例:添加带回调的Section let section = FormSection_Model(title: "设置", items: [ FormItem(title: "重置", action: { [weak self] in self?.resetSettings() }) ]) formController?.sections.append(section) - 手势/观察者:确认
hideKeyboardWhenTappedAround()添加的手势未被额外持有,必要时在控制器销毁前手动移除手势。
3. 完善子控制器移除流程
在移除子控制器时,提前置空TableView的代理与数据源,避免潜在的延迟释放:
func removeFromParentViewController() { guard parent != nil else { return } tableView.delegate = nil tableView.dataSource = nil willMove(toParent: nil) removeFromParent() view.removeFromSuperview() }
4. 用Instrument定位泄漏源头
通过Xcode的Leaks工具精准定位引用链:
- 打开Instrument并选择Leaks模板
- 操作主控制器进入再退出,观察
ChildController实例是否未被回收 - 查看泄漏实例的引用关系图,找到未释放的强引用源头
内容的提问来源于stack exchange,提问作者Santiago Padilla
相关产品推荐
相关产品推荐

