【发布时间】:2017-06-20 00:50:37
【问题描述】:
我知道有几个相关的问题,而且我可以在互联网上找到很多帖子。
但是,我无法理解闭包可以保存引用的事实。在引用类型的情况下,这是完全正常且非常合理的,但是值类型呢,包括struct 和enum?
请参阅此代码。
let counter: () -> Int
var count = 0
do {
counter = {
count += 1
return count
}
}
count += 1 // 1
counter() // 2
counter() // 3
我们可以通过两种方式访问值类型count。一种是直接使用count,另一种是通过闭包counter。
但是,如果我们写
let a = 0
let b = a
,在内存中b 当然与a 有不同的区域,因为它们是值类型。这种行为是值类型的一个显着特征,与引用类型不同。
然后回到闭包主题,闭包引用了值类型的变量或常量。
那么,我可以说在闭包捕获值的情况下,我们不能对值类型有任何引用的值类型的特性被改变了吗? 对我来说,捕获对值类型的引用非常令人惊讶,同时我上面展示的经验也表明了这一点。
你能解释一下这件事吗?
【问题讨论】:
-
我不确定您想要了解多少技术细节,但stackoverflow.com/a/40979548/2976878 和stackoverflow.com/q/43171341/2976878 可能有用。基本上,捕获的值被放入堆上的引用计数框,然后存储在函数值的上下文对象中。在捕获持有引用类型的变量时,引用本身被放入堆分配的盒子中(考虑到引用 它们自己 是值类型),所以你实际上是对引用的引用.
-
我阅读了您的帖子,非常令人兴奋。我理解了内容。但是,什么是引用计数框?我读了你多次显示的答案,但我没有遇到这个词。你能解释一下吗?
-
很高兴您发现它很有用!盒子只是给定值的包装器,因此通过闭包捕获,捕获的变量的值被“装箱”在堆中的结构中,在我的回答中具有专门的
Box<T>结构的布局。然后(大部分)将来对该变量的任何访问(在它定义的范围内以及闭包中)都被编译为访问堆上的装箱值。 -
虽然这么说,但严格来说,在 your 示例中,
count是一个 global 变量,因此具有静态生命周期,这意味着闭包技术上不会捕获它——它只是正常访问它(如果你有兴趣,我会稍微介绍一下全局和静态访问器here的实现)。 -
但我离题了。在这种情况下,引用计数只是跟踪堆上内存块的生命周期的一种方式,顾名思义,它是通过对对该内存的引用数进行计数来完成的,当引用 == 0。Swift 使用 Automatic Reference Counting 来执行此操作。