Is weak self needed for table view cell button closure

吃可爱长大的小学妹 提交于 2021-01-28 11:34:51

问题


In trying to avoid retain cycles, would using [weak self] in in a UITableViewCell button action be necessary? Example:

in ViewController's cellForRow

cell.buttonAction = { (cell) [weak self] in
     self.someFunction()
}

in TableViewCell class

var buttonAction: ((UITableViewCell) -> Void)?

@IBAction func buttonPressed(_ sender: Any) {
     buttonAction?(self)
}

回答1:


The key line to think about is:

var buttonAction: ((UITableViewCell) -> Void)?

You are offering to store a function long-term in an instance property.

Now think about who refers to / owns whom. The view controller owns its view which is-or-owns the table view which owns the cell. Meanwhile the cell owns the function. If the function refers strongly to any of the objects I just mentioned, that is a retain cycle. It is a classic retain cycle, the absolute model of how retain cycles get made.


[I would like to add a word about how I test for these things. There's a really cheap and easy way: wrap your view controller up in a navigation controller plus a blank root view controller, so that you can push your view controller onto it. Implement deinit in your view controller. Now run the app, push your view controller, play with it for a bit, and pop it with the Back button. If deinit isn't called, you've got a retain cycle.]




回答2:


Yes, it is necessary to use unowned or weak to capture self in this case.

  • Your view controller will most likely have a strong reference to the UITableView
  • The table view has strong reference to it's UITableViewCells and
  • Each cell has strong reference to your buttonAction closure.

Using self directly will have as an effect a retain cycle.

This is actually pretty easy to test. Try to present the following view controller and dismiss it:

class TestTableViewCell: UITableViewCell {
    var closure: (() -> Void)?
    
    deinit {
        print("TestTableViewCell deinit!")
    }
}

class TestTableViewController: UITableViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.register(TestTableViewCell.self, forCellReuseIdentifier: "TestTableViewCellIdentifier")
    }
    
    deinit {
        print("TestTableViewController deinit!")
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableViewCellIdentifier", for: indexPath) as! TestTableViewCell
        
        cell.closure = { [weak self] in
            guard let self = self else { return }
            self.testFunction()
        }
        
        return cell
    }
    
    func testFunction() {}
}

// Test present
let controller = TestTableViewController()
present(controller, animated: true) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        self.dismiss(animated: true)
    }
}

You will have the following output:

TestTableViewController deinit!
TestTableViewCell deinit!

Now presenting the same view controller without weak and you will see that there is no output, meaning that the deinit functions are not get called and the objects stay in the memory.




回答3:


if you're not using the object of tableviewcell and you only want execute action on the cell make this.

var buttonAction: (() -> Void)?

@IBAction func buttonPressed(_ sender: Any) {
     buttonAction?()
}

and on the cellForRow use this,

cell.buttonAction = { [weak self] in
     self?.someFunction()
}

for check retain cycles, I usually use this option for check if the memory is retain or not.



来源:https://stackoverflow.com/questions/64297651/do-closures-inside-collectionview-cellforitemat-need-weak-self

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!