Search code examples
iosswiftuitableviewdiffabledatasource

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.


Solution

  • 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.