在所有成员初始化之前被闭包自我捕获 - 但我确实初始化了它们

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

这是一个玩具示例,但它准确地减少了我所处的情况:

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
        }
    }
}

我正在尝试使我的 table 视图数据源独立,而我这样做(到目前为止)的方法是子类化 UITableViewDiffableDataSource。这工作正常,除非我尝试给我的子类一个自定义初始化程序。玩具示例显示了问题。

我想要填充单元格的方式完全取决于一个可以在数据源生命周期后期更改的值。因此,它不能硬编码到单元提供程序函数中。我不能在这里简单地引用 string,这是在初始化程序中传递的值;我必须引用 self.string 因为其他代码稍后将有权更改此数据源的 string 实例 属性,并且我希望单元格提供者在那个时候使用那个新值发生了。

但是,我收到错误 "self captured by a closure before all members were initialized"。这似乎不公平。在调用 super.init 之前,我 做了 初始化了我的 string 实例 属性。因此,它在可能调用单元格提供程序方法的最早时刻确实具有价值。

您可以通过 tableView.datasource 访问 self,它会解决大部分问题。

虽然我不完全确定 为什么 Swift 不允许这样做(与捕获 self 以在实际调用 super.init ),我至少知道一个解决方法。改为捕获一个弱局部变量,并在调用 super.init 后将该局部变量设置为 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
    }
}

唯一的问题是,如果闭包在 super.init 的调用期间执行,那么 selfWorkaround 将在内部为 nil关闭,你可能会得到意想不到的结果。 (不过,在这种情况下,我不认为它是 - 所以你这样做应该是安全的。)

编辑:我们制作局部变量weak的原因是为了防止self对象被泄露。

扩展 Abhiraj Kumar 从 2020 年 2 月 16 日开始的回答,这里有一个使用提供的 TableView 来“返回”以获取附加到 table 的数据源的示例...即“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
        }
    }
}