You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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工具精准定位引用链:

  1. 打开Instrument并选择Leaks模板
  2. 操作主控制器进入再退出,观察ChildController实例是否未被回收
  3. 查看泄漏实例的引用关系图,找到未释放的强引用源头

内容的提问来源于stack exchange,提问作者Santiago Padilla

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 08:45:03