In cellForRow we give the cell a closure to cause the ViewController to doSomething.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier") as? CustomCell else {
            return UITableViewCell()
        }
        
        cell.closure = {
            doSomething()
        }
        
        return cell
    }

 

The cell needs a variable to store the closure it gets handed.

class CustomCell: UITableViewCell {

    var closure: (() -> ())?

 

You might’ve noticed Xcode complained.

Self Capture Semantics

 

Memory Leaks

This means that the closure handed to the cell will “capture” the ViewController. In other words, it will hold a strong reference to it. The ViewController is the TableViewDelegate for those cells, so it will also hold strong references to them. Since both maintain strong references to each other, their references are circular, causing a “retain cycle”.

 

Retain Cycles Swift

 

Automatic Reference Counting (ARC) is a mechanism built into the Objective-C runtime that is responsible for memory management in iOS applications. ARC cleans up objects floating around in memory when nothing has a strong reference to them. ARC checks how many other objects reference some object, and if that count is zero it deallocates that object from memory.

Circular references cause counts to stay above zero so objects are never released from memory.

 

A Problematic Solution

We can force the self reference to be weak like this:

cell.closure = { [weak self] in
    self?.doSomething()
}

 

But we cannot do this:

func doSomething() {
    [weak self] in
}
    
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier") as? CustomCell else {
        return UITableViewCell()
    }
    
    cell.closure = doSomething
    
    return cell
}

 

… because you can’t use closure syntax for [weak self] inside of a normal function.

Closure Syntax Problem

 

You have to wrap the calling on doSomething inside of a closure that you hand to the cell. So instead of passing the original function around, we have to wrap it inside a closure and pass the closure.

cell.closure = { [weak self] in
    self?.doSomething()
}

 

If that TableViewCell contains a CollectionView, it might want to hand the action to each of its cells. Each time the function gets passed around - to keep it from leaking memory - you’ll need to wrap each closure inside of another closure to keep the references weak.

collectionViewCell.closure = { [weak self] in
    self?.closure() // self == the TableViewCell
}

So now doSomething() is wrapped inside of two closures: closure(closure(function)). Even worse, if you want to pass or receive variables from this function, it quickly becomes very hacky and complex, and therefore difficult to understand and maintain.

 

A Better Solution

If instead we could pass the original function doSomething(), then using it one, two, or ten levels deep would be the same as calling it directly from the top ViewController, but we need to make sure the references to that closure are always weak. Unfortunately, you can’t declare a closure variable with a weak keyword like this:

weak var closure: (() -> ())?

So we need to force the “weak” reference ourselves. Simply set closure = nil when the view dissapears. For example, in the ViewController:

override public func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    closure = nil // remove the reference
}

Or any subclass of UIView:

override func willMove(toWindow newWindow: UIWindow?) {
    super.willMove(toWindow: newWindow)
    
    if newWindow == nil {
        closure = nil // remove the reference
    }
}

When willMoveTo(newWindow) == nil the view was removed from the view heirarchy. This seems like a good time to remove the reference and allow ARC to deallocate it. If this was a TableViewCell for example, when the ViewController dequeues the cell again it will be handed a new closure.

Simple and effective.

MIT License.