2017-06-20 32 views
2

我知道有几个相关的问题,而且我可以在互联网上找到很多帖子。 但是,我不明白封闭可以持有引用的事实。在参考类型的情况下,这是完全正常且非常合理的,但值类型如何,包括structenum? 请参阅此代码。Swift:闭包是否有对常量或变量的引用?

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,因为他们是值类型。并且这种行为是与参考类型不同的值类型的明显特征。 然后回到闭包主题,闭包有对值类型的变量或常量的引用。

那么,我可以说值类型的功能,我们不能有任何引用的值类型改变的情况下封闭的捕获值? 对我来说,捕获对值类型的引用是非常令人惊讶的,同时我上面显示的经验表明这一点。

你能解释这件事吗?

+2

我不确定你想获得多少技术细节,但https://stackoverflow.com/a/40979548/2976878&https://stackoverflow.com/q/43171341/2976878可能是有用。基本上,捕获的值被放入堆中的引用计数框中,然后存储在函数值的上下文对象中。当涉及到捕获具有引用类型的变量时,引用本身被放入堆分配框(考虑引用*本身*是值类型),所以您实际上是对引用的引用。 – Hamish

+0

我看过你的文章,这很刺激。我了解内容。但是,什么是参考计数盒?我读了你多次展示的答案,但我没有见过这个词。你能解释一下吗? –

+0

很高兴你发现它很有用!一个盒子只是一个给定值的包装,所以在闭包捕获时,捕获的变量的值在结构中堆积为“盒子”,并在我的答案中包含专门的“盒子'结构的布局。然后(主要)对该变量的任何未来访问(在其定义的范围内以及闭包中)仅被编译为访问堆上的装箱值。 – Hamish

回答

1

我认为混乱是由于太过努力的价值类型与参考类型。这与此无关。让我们将数字作为参考类型:

class RefInt: CustomStringConvertible { 
    let value: Int 
    init(value: Int) { self.value = value } 
    var description: String { return "\(value)" } 
} 

let counter:() -> RefInt 
var count = RefInt(value: 0) 
do { 
    counter = { 
     count = RefInt(value: count.value + 1) 
     return count 
    } 
} 
count = RefInt(value: count.value + 1) // 1 
counter() // 2 
counter() // 3 

这种感觉有什么不同吗?我希望不是。这是同样的事情,只是在参考。这不是一个价值/参考的东西。

问题是,正如你注意到的那样,闭包捕获变量。不是变量的值,或变量指向的引用的值,而是变量本身)。因此,在所有其他捕获该变量的位置(包括调用者)中都会看到对闭包内变量的更改。在Capturing Values中对此进行了更全面的讨论。如果你有兴趣(我现在进入有点技术性,可能会超出你在乎什么,现在的)

深一点:

瓶盖居然有变量的引用,并改变它们会立即发生,包括调用didSet等。这与inout参数不同,该参数仅在返回时才将值分配给其原始上下文。你可以看到,这种方式:

let counter:() -> Int 
var count = 0 { 
    didSet { print("set count") } 
} 

do { 
    counter = { 
     count += 1 
     print("incremented count") 
     return count 
    } 
} 

func increaseCount(count: inout Int) { 
    count += 1 
    print("increased Count") 
} 

print("1") 
count += 1 // 1 
print("2") 
counter() // 2 
print("3") 
counter() // 3 

increaseCount(count: &count) 

这版画:“增加数量”

1 
set count 
2 
set count 
incremented count 
3 
set count 
incremented count 
increased Count 
set count 

注“设定计数”怎么总是前“增加的计”,但后这使得闭包真正引用了它们捕获的同一个变量(而不是值或引用;变量),以及为什么我们称它为捕获闭包,而不是“传递”到函数。 (当然,您也可以“通过”关闭,在这种情况下,它们的行为与这些参数的功能完全相同。)

+0

谢谢你的回答!但是,我仍然不清楚。 “封闭捕获变量”是什么意思?我无法想象这很好。如你所知,''''''实例的变量包含实例的内存地址并且位于堆栈区域。另一方面,与值类型对应的变量是值本身,也在堆栈区域中。闭包捕获什么时闭包有什么作用?你解释了变量本身,但是如果你给我更详细的解释,封闭是如何产生的? –

+0

有关实施讨论,请参阅上面他评论中的Hamish链接。 (捕获的变量被放置在堆的参考框中。)但重要的区别是“与值类型对应的变量”不是*“值本身”。它是一个名称,*表示一个值,可以改变它指向的值。这个非常重要。如果它是“价值本身”,那么var就没有意义。 ('let'可以这样想,只是一个“绑定”而不是一个引用,当所有的东西都是'let'时,这些问题中的大多数都会消失。) –

+0

变量('var')是一个值的引用,而不是价值本身。 “参考”这个词被用于许多事情,这可能会让人困惑。例如,仅仅因为某些东西是“价值类型”并不意味着它具有“价值语义”,反之亦然。例如,'NSDate'是一个引用类型,但是具有值语义。用引用语义构建一个结构(值类型)是相当容易的。无论这些东西是堆栈还是堆,都完全是一个实现细节(并且Swift在堆栈和堆之间移动了一些东西)。某些类型不在堆栈中或堆中(标记的指针)。 –