Imanou Petit
Imanou Petit

Reputation: 92419

Strong reference cycle: closures vs methods

I have a project that contains a UITableViewController called TableViewController. Because I want my UITableViewDataSource protocol declaration to be outside of my TableViewController declaration, I've set the following code (inspired by Objc.io Lighter View Controllers):

TableViewController:

class TableViewController: UITableViewController {

    let array = [["1"], ["2", "3", "4"], ["5", "6"]]
    var dataSource: DataSource!


    override func viewDidLoad() {
        super.viewDidLoad()

        dataSource = DataSource(array: array, configureCellBlock: { (cell, item) in
            cell.textLabel.text = item
        })
        tableView.dataSource = dataSource
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    deinit {
        println("Quit TVC")
    }

}

DataSource:

class DataSource: NSObject, UITableViewDataSource {

    let array: [[String]]
    typealias TableViewCellConfigureBlock = (cell: UITableViewCell, item: String) -> ()
    var configureCellBlock: TableViewCellConfigureBlock


    init(array: [[String]], configureCellBlock: TableViewCellConfigureBlock) {
        self.array = array
        self.configureCellBlock = configureCellBlock

        super.init()
    }

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return array.count
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return array[section].count
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell

        let data = array[indexPath.section][indexPath.row]
        configureCellBlock(cell: cell, item: data)

        return cell
    }

    deinit {
        println("Quit DataSource")
    }

}

This works fine. But now, I want to replace the configureCellBlock closure with a method. So I've changed my TableViewController code to this:

class TableViewController: UITableViewController {

    let array = [["1"], ["2", "3", "4"], ["5", "6"]]
    var dataSource: DataSource!


    override func viewDidLoad() {
        super.viewDidLoad()

        dataSource = DataSource(array: array, configureCellBlock: formatCell)
        tableView.dataSource = dataSource
    }

    func formatCell(cell: UITableViewCell, item: String) -> () {
        cell.textLabel.text = item
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    deinit {
        println("Quit TVC")
    }

}

The problem now is obvious: if I run this code, TableViewController and DataSource are never deallocated because of a strong reference cycle.

I've been trying to change my dataSource declaration to weak var dataSource: DataSource! or unowned var dataSource: DataSource but none of my recent tries worked.

How can I replace my configureCellBlock closure with a method? Do I have to use a protocol delegate pattern to do so? What would it look like?

Upvotes: 1

Views: 347

Answers (2)

Rob
Rob

Reputation: 437492

The problem is that the reference to formatCell has an implied reference to self. This is not resolved by making the data source weak (you definitely want a strong reference there), but rather making sure that the block variable in the data source does not maintain a strong reference back to the view controller. So, you'd add [unowned self] to the start of the closure:

dataSource = DataSource(array: array) {
    [unowned self] cell, item in

    self.formatCell(cell, item: item)
    return
}

Upvotes: 2

lassej
lassej

Reputation: 6494

You can implement it with a delegate like this:

@objc protocol TableViewCellConfigurator {
  func dataSource( dataSource: DataSource, configureCell cell: UITableViewCell, item: String)
}

class DataSource: NSObject, UITableViewDataSource {
  weak var cellConfigurator: TableViewCellConfigurator?

  (...)

  func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
    let data = array[indexPath.section][indexPath.row]
    if let delegate = cellConfigurator {
      cellConfigurator.dataSource( self, configureCell: cell, item: data)
    }
    return cell
  }

  (...)

}


class TableViewController: UITableViewController: TableViewCellConfigurator {

  override func viewDidLoad() {
    super.viewDidLoad()

    dataSource = DataSource(array: array, configureCellBlock: formatCell)
    tableView.dataSource = dataSource
    dataSource.cellConfigurator = self
  }

  override func dataSource( dataSource: DataSource, configureCell cell: UITableViewCell, item: String) {
    cell.textLabel.text = item
  }
}

Upvotes: 1

Related Questions