【问题标题】:self captured by a closure before all members were initialized - but I did initialize them在所有成员初始化之前由闭包自我捕获 - 但我确实初始化了它们
【发布时间】:2020-05-31 19:07:26
【问题描述】:

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

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

我正在尝试使我的表视图数据源自包含,而我这样做的方式(到目前为止)是将 UITableViewDiffableDataSource 子类化。这很好用,除非我尝试给我的子类一个自定义初始化程序。玩具示例显示了问题。

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

但是,我收到错误“在所有成员初始化之前由闭包自我捕获”。这似乎不公平。我确实在调用super.init 之前初始化了我的string 实例属性。因此,它在可能调用单元格提供程序方法的最早时刻确实具有价值。

【问题讨论】:

  • This 看起来相关。
  • @MartinR 当然,我发现了很多关于此错误消息的问题,但它们不适用于我的情况(即没有我可以使用的解决方案)。

标签: ios swift uitableview diffabledatasource


【解决方案1】:

虽然我不完全确定 为什么 Swift 不允许这样做(与在实际调用 super.init 之前捕获 self 以创建闭包有关),我至少知道它的解决方法。而是捕获一个弱局部变量,并在调用 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 将为零,您可能会得到意想不到的结果。 (不过,在这种情况下,我不认为它是 - 所以你应该安全地这样做。)

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

【讨论】:

  • 非常有趣的想法。你是说selfWorkaround 被闭包捕获并在闭包捕获空间中保持活动状态——因此它继续指向self,即使selfWorkaround 在被捕获时是一个局部变量。
  • 是的!我刚刚对其进行了测试以确保我没有发疯,并且它确实打印出了在调用闭包时它将为self 打印的内容(在数据源对象上调用apply 之后)。
  • 在我的真实用例中对其进行了测试,效果很好(即编译并且运行正常)。这真是太可怕了。我可以安全地在编译器的自我保护概念中“打一个洞”这一事实强化了我的建议,即编译器是错误的。我想知道我是否应该提交错误报告。 — 另外,我有一种有趣的感觉,我们现在正在泄漏 MyDataSource。以防万一,我将selfWorkaround 标记为weak 并没有损坏。
  • 哇哦,你是对的 - 这确实泄露了MyDataSource!我将更新我的答案以使其成为weak。好收获!
【解决方案2】:

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

【讨论】:

  • 这是一个非常有趣的想法。我会试一试。当然,如果这是合法的,那就表明这确实是 Swift 中的一个错误。
  • 这是合法有效的方式。您正在尝试购买通行证的 init 快速过程。这不是错误。您正在查找地址尚未分配的东西的地址。
  • 编译器错误是我建议的错误。这个初始化器的闭包 CellProvider 应该是@escaping;在初始化完成之前它不会被调用,因此,像lazy,计算变量一样,它应该被允许引用self,因为self在时间到来时存在。
  • 真正的错误/API 设计问题是 UITableViewDiffableDataSource 需要 CellProvider 闭包作为 init() 参数,但该闭包不能引用 self.anything ...这意味着 CellProvider 可以在生成单元格时不要询问有关数据上下文的任何问题......这是一个愚蠢的设计。应该能够 init() 对象,然后在 super.init() 之后将 .cellProvider 设置为闭包
  • @BillPatterson 是的,就像节提供者已经发生的那样。好点。
【解决方案3】:

扩展 Abhiraj Kumar 从 2020 年 2 月 16 日起的回答,这里有一个使用提供的 TableView 来“返回”以获取您附加到表格的数据源的示例......即“自我”:

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

【讨论】:

    猜你喜欢
    • 2018-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-06
    • 2022-01-15
    相关资源
    最近更新 更多