Reputation: 92419
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
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
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