How do I inject reference types in a table view cell and reload the row of the said cell without causing a memory leak?

For instance, executing the following code, in which a stepper is injected in a table view cell and the cell is reloaded when the user changes the stepper's value, causes the memory usage to grow pretty quickly (I stopped the simulation at 1GB) when you tap on the stepper.

Also the CPU usage jumps straight at 99%, and the UI freezes.

Note: I'd like to know exactly what I asked, not how to make a table view cell with a stepper in general.

I know that calling reloadData() or reconfigureRows(at:) doesn't cause any of the mentioned issues.

Also please don't reply with questions like "Have you tried to use weak references?".

The code is short: please reply with a working solution if you can.

class ViewController: UIViewController {
    let tableView = UITableView()
    let stepper = UIStepper()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(tableView)
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.dataSource = self
        
        stepper.addTarget(self, action: #selector(stepperValueChanged), for: .valueChanged)
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        tableView.frame = view.bounds
    }
    
    @objc private func stepperValueChanged() {
        tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        1
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.accessoryView = stepper
        var configuration = cell.defaultContentConfiguration()
        configuration.text = "\(stepper.value)"
        cell.contentConfiguration = configuration
        return cell
    }
}

Replies

Using reload instead of reconfigure is the problem here. Reloads have to create a new cell for every index path for which a reload is requested, and the existing cell is just placed in the reuse queue, until the system receives a memory pressure notification or you call reloadData. If you cannot use reconfigure because you're targeting something lower than iOS 15, then you should fetch the cell from UITableView yourself using UITableView.cellForRow(at:), and then update its contentConfiguration yourself.

Okay thank you.

I don’t understand why the memory keeps growing, but I understand that I simply cannot use reloadRows(at:) in this case.