Background:
I designed a TableViewDataSource
class that provides an implementation for UITableViewDataSource
and UITableViewDelegate
. You instantiate TableViewSection
objects, which are passed to the TableViewDataSource
which are used to configure cells, section headers, handle selection, row insertion, etc.
The TableViewSection
object has a property called dataSource: [AnyObject]?
, which, when set, is used to calculate the number of rows in the section, and provide an object for the cell configuration block:
// get the section, dequeue a cell for that section, retrieve the item from the dataSource
// ...
tableSection.cellConfigurationBlock?(cell: AnyObject, item: AnyObject?, indexPath: NSIndexPath)
return cell
What I'd like to do is assign a reference to an array from my viewModel
to my tableSection.dataSource
, having my viewModel
update the array, in turn updating the table view. In Swift, you cannot pass an array by reference. The workaround seems to be to use an NSMutableArray
, but with that comes a loss of type safety, and greater cognitive load while translating objects back and forth from Swift to Foundation.
Working Example:
let kCellIdentifier = "SomeCellIdentifier"
class MyViewController: UITableViewController {
// Property declarations
@IBOutlet var tableDataSource: TableViewDataSource!
var viewModel: MyViewControllerViewModel = MyViewControllerViewModel()
override func viewDidLoad() {
super.viewDidLoad()
self.setupTableView()
self.refresh()
}
func setupTableView() {
var tableSection = TableViewSection(cellIdentifier: kCellIdentifier)
tableSection.dataSource = self.viewModel.collection
// tableSection configuration
// ...
self.tableDataSource.addSection(tableSection)
}
func refresh() {
self.viewModel
.refresh()
.subscribeNext({ result in
self.tableView.reloadData()
}, error: { error in
self.logger.error(error.localizedDescription)
})
}
}
The refresh()
method on the viewModel
hits my API service, updates it's collection
property on response, and provides the result on the next
event of an RACSignal
(RACSignal
is a class provided by Reactive Cocoa and really, besides the point).
I've found one workaround, which involves reassigning the data source each time a single update is made, or after a batch update.
func refresh() {
self.viewModel
.refresh()
.subscribeNext({ result in
self.updateDataSource()
self.tableView.reloadData()
}, error: { error in
self.logger.error(error.localizedDescription)
})
}
func updateDataSource() {
self.tableDataSource.tableSectionForIndex(0)?.dataSource = viewModel.collection
}
This approach works, but only temporarily as a workaround. As a TableViewDataSource
grows and becomes more complex, this method becomes increasingly more complex with imperative, procedural code, the opposite of what I set out to achieve when writing the class.
Question
Is there any workaround to stick to native Swift Array
's to achieve the equivalent of passing a Foundation NSArray
or NSMutableArray
by reference?
Bonus Question
Can someone provide me with some class/struct design tips to accomplish the desired goal in pure Swift?
See Question&Answers more detail:
os