Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

self captured by a closure before all members were initialized - but I did initialize them

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.

like image 296
matt Avatar asked Feb 16 '20 19:02

matt


3 Answers

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.

like image 104
TylerP Avatar answered Nov 06 '22 09:11

TylerP


you can access self via tableView.datasource and it will sort most of the problem.

like image 42
Abhiraj Kumar Avatar answered Nov 06 '22 10:11

Abhiraj Kumar


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
        }
    }
}
like image 2
Bill Patterson Avatar answered Nov 06 '22 11:11

Bill Patterson