This is a toy example but it reduces exactly the situation I'm in:
class MyDataSource: UITableViewDiffableDataSource<String,String> {
var string : String?
init(string:String?) {
self.string = string
super.init(tableView: UITableView()) { (_, _, _) -> UITableViewCell? in
print(self.string) // error
return nil
}
}
}
I'm trying to make my table view data source self-contained, and my way of doing that (so far) is to subclass UITableViewDiffableDataSource. This works fine except when I try to give my subclass a custom initializer. The toy example shows the problem.
The way I want to populate the cell absolutely depends upon a value that can change later in the life of the data source. Therefore it cannot be hard-coded into the cell provider function. I cannot refer here simply to string
, the value that was passed in the initializer; I must refer to self.string
because other code is going to have the power to change this data source's string
instance property later on, and I want the cell provider to use that new value when that happens.
However, I'm getting the error "self captured by a closure before all members were initialized". That seems unfair. I did initialize my string
instance property before calling super.init
. So it does have a value at the earliest moment when the cell provider method can possibly be called.
While I'm not entirely sure why Swift doesn't allow this (something to do with capturing self
to create the closure before the actual call to super.init
is made), I do at least know of a workaround for it. Capture a weak local variable instead, and after the call to super.init
set that local variable to self
:
class MyDataSource: UITableViewDiffableDataSource<String,String> {
var string : String?
init(string:String?) {
self.string = string
weak var selfWorkaround: MyDataSource?
super.init(tableView: UITableView()) { (_, _, _) -> UITableViewCell? in
print(selfWorkaround?.string)
return nil
}
selfWorkaround = self
}
}
The only problem with this, though, is that if the closure is executed during the call to super.init
, then selfWorkaround
would be nil inside the closure and you may get unexpected results. (In this case, though, I don't think it is - so you should be safe to do this.)
Edit: The reason we make the local variable weak
is to prevent the self
object from being leaked.
you can access self
via tableView.datasource
and it will sort most of the problem.
Expanding on Abhiraj Kumar's answer from Feb 16 2020, here's an example of using the TableView provided to "reach back" to get the data source you attached to the table... i.e. "self":
class MyDataSource: UITableViewDiffableDataSource<String,String> {
var string : String?
init(string:String?) {
self.string = string
super.init(tableView: UITableView()) { (tableView, _, _) -> UITableViewCell? in
// Very sketchy reach-through to get "self", forced by API design where
// super.init() requires closure as a parameter
let hack_self = tableView.dataSource! as! MyDataSource
let selfDotStr = hack_self.string
print("In closure, self.string is \(selfDotStr)")
return nil // would return a real cell here in real application
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With