我的变量存储在哪里?(Swift)

10

这是我用Swift进行的一个小实验:

func store<T>(var x: T) -> (getter: (Void -> T), setter: (T -> Void)) {
    return ({ x }, { x = $0 })
}

x 是一个值类型。

我的问题是:

  • x 究竟存储在哪里(栈/堆)?
  • 以这种方式存储 x 的缺陷是什么?
  • 这样做是安全的吗?
  • x 何时会被销毁(如果有)?
1个回答

15

参数通过值传递给函数和方法——这意味着参数的副本会被创建并在函数体中使用。

函数和方法接收到的参数是不可变的,这意味着它们的值无法更改。然而,var修饰符可以使参数变为可变——需要注意的重要一点是:参数的副本是可变的,传递给函数的参数与函数体接收到的参数没有关系,除了最初的副本。也就是说,通过var修饰符使参数可变会使其发生变化,但其生命周期仅限于函数体,不会影响传递给函数的原始参数。

还有另一种选择,即inout修饰符,它的工作方式类似于var,但当函数返回时,值将被复制回传递的变量中。

值得一提的是,到目前为止,我隐含地只考虑了值类型。如果将引用类型实例(类或闭包)作为var参数传递给函数,则通过该参数进行的任何更改实际上都是针对传递给函数的实例进行的(这是值类型和引用类型之间最显着的区别)。由x变量指向的实例与传递给函数的参数具有相同的生命周期。

所有这些说法,在你的情况中,它的工作方式略有不同。你正在返回一个闭包(好吧,它们是2个,但这并不改变结论),该闭包捕获x,从而导致x在变量分配闭包的范围内保持活动:

let x = 5
let (getter, setter) = store(x)

在上述代码中,当gettersetter被释放时,x(作为在store函数中定义的变量)也将停止存在。

回答你的问题:

  1. x是在调用store函数时创建的变量。由于您明确提到值类型,x应该分配在堆栈上(与应该用于引用类型的堆相对)
  2. 陷阱是当2个返回值(它们都是闭包引用类型)被释放时,x也会被释放
  3. 这在某些特定情况下可能有用,但通常我会避免使用它-请注意,这是我的个人意见
  4. 如上所述(当函数返回值被释放时)

非常精彩的措辞,我真的很感激你花时间写这篇文章!然而,我不理解你所描述的陷阱背后的理由。 - Vatsal Manot
应该更简单:如果 T 是值类型,那么变量将分配在堆栈上,但是闭包(作为引用类型)将分配在堆上。因此,数据存储在堆栈上,并由分配在堆上的两个实例保持活动状态。 - Antonio
请注意,标准 Swift 数组的 COW 实现可能会将其数据存储在堆上,即使数组本身是值类型。 - Max Desiatov
据我所知,inout并不是通过引用传递:它会先复制一份,然后再复制回去。(至少在与值一起使用时是这样的) - oisdk
2
@oisdk 是的,你说得对 - 感谢你指出来。我已经在答案中进行了修正。我默认假设 & 自动转换为“引用” - 太多年花在 C++ 上了 :) - Antonio
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接