不小心改变了结构的副本而不是结构本身

Accidentally mutating a copy of a struct instead of the struct itself

使用身份类型编程多年,我发现使用变异值类型非常有压力,因为经常存在意外分配(并因此复制)到新变量然后变异该变量的风险 copy 并期望看到这些更改反映在原始结构中(最后给出的示例)。

我的实际问题有什么approaches/codingstyles/practices可以防止这种事情发生?


我在想什么:我无法想象这个问题的答案只是 "just remember that you're dealing with a struct and not a class",因为这非常容易出错,尤其是因为 class-ness/struct-ness 对于使用任何特定数据结构的代码来说是完全不透明的。

我也看到很多关于使用“purely functional style”的讨论,除了:

我的问题的一些可能答案可能是:

这些方向是否正确?


示例:假设我们有一个结构,它简单地包装一个整数,它使用变异方法 next().

递增 1
struct Counter {
    var i: Int = 0

    mutating func next() -> {
        self.i += 1
        return self.i
    }
}

在某处的某个初始化程序中,我们像这样实例化

self.propertyWithLongAndAnnoyingName = Counter()

之后在同一范围内,忘记了这个 属性 包含一个结构,我们将它分配给简洁命名的局部变量 p 并递增 that.

var p = self.propertyWithLongAndAnnoyingName
d.next() // actually increments a copy

你的问题,如果它不仅仅是一个精心设计的巨魔,似乎更多的是心理上的而不是实际的,因此可能不适合 Stack Overflow:这是一个见仁见智的问题。但是,认真对待您并假设问题是真诚的,我可以根据经验告诉您,作为一个在 Objective-C 编程多年然后在 Swift 首次发布时学习的人public,值类型对象(如结构)的存在带来了巨大的缓解,而不是你似乎提出的问题。

在 Swift 中,结构是对象类型 卓越。当您只需要一个对象时,您可以使用结构。使用 classes 是非常特殊的,仅限于以下情况:

  • 您正在使用 Cocoa(它是用 Objective-C 编写的,您只需要接受它的对象是 classes)

  • 你需要一个 superclass / subclass 关系(在纯 Swift 编程中很少见;通常这只是因为你正在使用 Cocoa)

  • 对象是为了表示外部现实,其中个体身份至关重要(例如,UIView 需要是引用类型,而不是值类型,因为确实有个体视图位于接口,我们需要能够区分它们)

  • 真正的可变性是迫切需要的

因此,一般来说,大多数程序员的感受与您假设的心态恰恰相反:当一个对象意外地变成引用类型(class)和变异时,他们会感到惊讶影响远程引用。在您给出的代码示例中:

var p = self.propertyWithLongAndAnnoyingName
p.next()

...如果self.propertyWithLongAndAnnoyingName也发生变异,那将是一个彻底的震惊。值类型代表一种 安全 的形式。 p 的赋值和 p 的变异恰恰是 隔离 self.propertyWithLongAndAnnoyingName 不被更改。

事实上,Cocoa 本身竭尽全力 反对 这种事情。这就是为什么,例如,模式是 propertyWithLongAndAnnoyingName 永远不会(从外面看)一个 NSMutableString —— 它会是一个 NSString,它在对象周围设置了一个不可变的栅栏,即使它是一个参考类型。因此,在 Swift 中使用结构 解决了 一个 Cocoa 必须通过更精细和(对许多初学者来说)神秘的措施来解决的问题。

但是这样的情况在实际中是比较少见的,因为你的假设的另一个方面也是错误的:

I can't imagine the answer to this question simply being "just remember that you're dealing with a struct and not a class", because that's extremely error prone especially since class-ness/struct-ness is entirely opaque to code using any particular data structure

我自己的经验是,在任何重要的情况下,我 总是 知道我是在处理结构还是 class。简单地说,如果它是一个 Swift 库对象(String、Array、Int 等),它就是一个结构。 (该库基本上根本没有定义 class。)如果它是一个 Cocoa 对象(UIView、UIViewController),它是一个 class.

确实,命名约定通常对您有帮助。 Foundation overlay 就是一个很好的例子。 NSData 是一个 class;数据是一个结构。

还请注意,您不能改变对结构的let引用,但可以改变[=16] =] 参考 class。因此在实践中你很快就会体验到哪个是哪个,因为如果可以的话你总是使用let

最后,如果将对象交给第三方进行就地突变对您来说真的很重要,那就是 inout 的目的。在那种情况下,您知道,因为您必须显式传递引用(地址)。如果您传递给的参数是 而不是 inout,您将假设您的原始对象不会发生突变,您会很高兴。