【问题标题】:Why does closure capture reference while function does not? Also, why "lazy" keyword is required for closure declaration?为什么闭包捕获引用而函数没有?另外,为什么闭包声明需要“惰性”关键字?
【发布时间】:2019-07-19 18:41:55
【问题描述】:

我在 Xcode Playground 中用以下代码进行了实验:

class X {

    var a = 3

    init(a: Int) {
        self.a = a
    }

    deinit {
        print("\(self.a) is deallocated.")
    }

    func returnX() -> Int {
        return self.a
    }

    lazy var anotherReturnX: () -> Int = {
        return self.a
    }
}

var e: X? = X(a: 6)
print(e!.returnX())
e = nil // prints "6 is deallocated."

var f: X? = X(a: 7)
print(f!.anotherReturnX())
f = nil // prints nothing

从上面的代码中,我可以看到函数returnX() 中没有捕获任何引用,因此一旦我将e 设置为nile 就会被释放。但是,在闭包anotherReturnX() 中捕获了一个引用,因此f 不会被释放。显然,这意味着闭包会捕获引用,而函数不会。

另外,当我第一次输入代码时,我没有在闭包声明之前包含 lazy 关键字,因为我认为没有必要这样做。但是它会触发编译时错误。我推断由于闭包只能在实例化后访问,它必须访问实例化的self。但是既然我在这里声明的实际上是一个“匿名函数”,为什么在实例化期间闭包访问self

经过一番思考,我发现了更多的矛盾。例如,我知道在调用闭包时会捕获引用。然而,在X 的初始化过程中,我只是分配一个闭包给一个变量而不调用它,与其他实例属性的声明相同。因此闭包在初始化期间不应该做任何事情,并且编译没有关键字lazy的代码应该没问题。但是编译失败。我不确定我的理解出了什么问题。

我已经阅读了一些相关的文章,例如强/弱引用、保留周期、惰性存储属性。然而,许多人解释“发生了什么”,并没有过多地谈论“为什么”。

所以除了标题中提出的问题之外,我还想澄清一下,是什么让函数和闭包彼此不同,从而导致上述情况发生?

更新:

闭包捕获引用而函数没有捕获引用这一事实在我身上进一步“强制”,因为根据 Xcode 编译器,我可以在 returnX() 中将 return self.a 重写为 return a,但我不能这样做anotherReturnX。因此,我想我不得不接受这一点,虽然函数和闭包是相似的,因为它们都是“一组功能”,但函数 与闭包不同,它不捕获引用。如果再深究这背后的原因,大概会涉及到 Swift 本身的设计吧?

但是,我还是不明白为什么闭包声明需要lazy关键字。

【问题讨论】:

标签: swift function closures lazy-evaluation conceptual


【解决方案1】:
lazy var anotherReturnX: () -> Int = {
    return self.a
}

这里的自我是一个坚强的自我。 当一个对象强引用另一个对象时,ARC 不能解除分配,因此会创建一个保留循环。引用应该是弱的,以避免通过在块内创建弱自身来避免保留循环。

lazy var anotherReturnX: () -> Int = { [weak self] in
    return self?.a
}

【讨论】:

    【解决方案2】:

    returnX 是类的一个方法。方法不捕获变量。 self是方法中的隐式局部变量,在方法调用时隐式传递给方法。

    anotherReturnX 是一个被延迟分配闭包的属性。该闭包捕获其中使用的外部变量,包括self。该捕获创建了从闭包到 X 实例的强引用,结合从 X 实例到闭包的强引用,创建了一个保留循环。

    【讨论】:

      猜你喜欢
      • 2011-12-12
      • 1970-01-01
      • 1970-01-01
      • 2021-08-04
      相关资源
      最近更新 更多