【问题标题】:Swift "Reference Cycles for Closures" Question(s)Swift“闭包的参考周期”问题
【发布时间】:2020-03-08 12:11:15
【问题描述】:

我想在 Swift 中尝试一些闭包循环。所以我从游乐场和example in the Swift documentation开始。

class HTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

var element = HTMLElement(name: "Travis", text: "Griggs")
print(element.asHTML()) // force the cycle
element = HTMLElement(name: "Bat", text: "Man") // encourage first element to deallocate

正如预期的那样,由于循环,没有提示 deinit。如果我只是将asHTML 初始化为一个不捕获自我的闭包,我就可以打破这个循环。例如

var element = HTMLElement(name: "Travis", text: "Griggs")
element.asHTML = { "No Cycles Here" }
print(element.asHTML()) // do the print
element = HTMLElement(name: "Bat", text: "Man") // encourage first element to deallocate

或者我可以将asHTML 默认初始化更改为包括:

{ [ weak self] in
    guard let self = self else { return "-from-the-dead-" }
    ...
}

也做正确的事。但是我认为可行的方法似乎行不通。我的许多闭包通常看起来像

{ self.doSomething() }

其中doSomething 基本上与闭包签名具有相同的签名。在这种情况下,似乎闭包只是一个额外的包装器。想象一下,如果我向HTMLElement 添加一个具有() -&gt; String 签名的方法:

extension HTMLElement {
    func defaultHTML() -> String {
        return "\(self.name) = \(self.text ?? "(there is no text)")"
    }
}

然后将asHTML 属性直接设置为:

var element = HTMLElement(name: "Travis", text: "Griggs")
element.asHTML = element.defaultHTML
print(element.asHTML())
element = HTMLElement(name: "Bat", text: "Man") // encourage first element to deallocate

出于某种原因,我认为这不会造成循环。但是我好像错了? element 不是 deinit。这有点违反直觉,因为我没有使用大括号来进行闭包。但似乎引用一个活动实例的方法正是这样做的?这是怎么回事?假设没有其他解释,在这种情况下有没有办法打破循环?或者我是否总是需要通过以下方式明确我的闭包:

element.asHTML = { [weak element] in element?.defaultHTML() ?? "-yo-text-be-gone-" }

【问题讨论】:

  • 方法和闭包(在大多数意义上)是同一件事,因此当您将函数存储为变量时,您正在捕获 self.defaultHTML(就像您引用类级属性一样) 并在 ARC 中创建另一个引用。这也让我头疼,我通常可以用两种方式说服自己,也必须进行 deinit 测试:-)。我喜欢这个作为参考:avanderlee.com/swift/weak-self
  • “所以我从操场开始” 不是手头的问题,但一般来说,不要那样做。 Playgrounds 会出于可视化目的引用您的对象。它们不是 ARC 行为的可靠描述。

标签: swift memory-management closures


【解决方案1】:

defaultHTML() 不仅仅是一个函数,它还是一个实例方法。它在一个实例上运行,在你的情况下,self。您的闭包不能只记住“只需调用defaultHTML”而不记得要调用哪个实例。

因此,当您创建像 { self.defaultHTML } 这样的闭包时,您正在创建一个堆分配的 ARC 管理的盒子,其中包含对 self 的强引用和指向 defaultHTML 实例方法实现的指针。

【讨论】:

  • 是的,我暗示我怀疑这就是正在发生的事情。这是有道理的。问题是,它是完全相同的机器吗?或者类似但不同的东西。更重要的是,如果想使用这种技术来减少样板类型,有没有办法打破循环?我看到的实例相关函数的捕获性质无法调整。
  • “问题,它是完全相同的机器吗?”相比什么? “为了减少样板类型,有没有办法打破循环?”没有。您弱捕获self 并在调用闭包时有条件地展开它的方法是目前正确的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-30
  • 1970-01-01
  • 1970-01-01
  • 2017-06-10
  • 2018-07-13
  • 2014-08-11
  • 1970-01-01
相关资源
最近更新 更多