Swift 意外地更改了结构的副本而不是结构本身

Swift 意外地更改了结构的副本而不是结构本身,swift,struct,immutability,mutable,Swift,Struct,Immutability,Mutable,多年来一直使用标识类型编程,我发现变异值类型的使用非常有压力,因为经常会有意外地分配(并因此复制)到一个新变量,然后变异该副本,并期望看到这些更改反映在原始结构中(最后给出的示例) 我的实际问题:有哪些方法/编码风格/实践可以防止此类事情发生 我的想法:我无法想象这个问题的答案仅仅是“请记住,您处理的是一个结构而不是一个类”,因为这极易出错,特别是因为类/结构对于使用任何特定数据结构的代码来说都是完全不透明的 我还看到很多关于使用“”的讨论,除了: 在变异方法还需要返回一个值的情况下(如我下

多年来一直使用标识类型编程,我发现变异值类型的使用非常有压力,因为经常会有意外地分配(并因此复制)到一个新变量,然后变异该副本,并期望看到这些更改反映在原始结构中(最后给出的示例)

我的实际问题:有哪些方法/编码风格/实践可以防止此类事情发生


我的想法:我无法想象这个问题的答案仅仅是“请记住,您处理的是一个结构而不是一个类”,因为这极易出错,特别是因为类/结构对于使用任何特定数据结构的代码来说都是完全不透明的

我还看到很多关于使用“”的讨论,除了:

  • 在变异方法还需要返回一个值的情况下(如我下面的示例所示),这样做似乎非常尴尬。当然,我可以返回一个元组,比如
    (newStructInstance,returnValue)
    ,但这不是方法
  • 我认为,无论何时,只要你想要突变,就实例化结构的新副本是不可能有任何效率的(与重新分配结构不同,据我所知,重新分配结构会创建一个新实例,而不会产生任何成本)
对于我的问题,一些可能的答案可能是:

  • 你的例子是人为的,这种事情在实践中很少发生
  • 您使用的是迭代器(anti)模式(参见下面的代码),因此这就是您的原罪
  • 减少
    mutating
    的麻烦,只需使用
    struct
    s表示不可变类型,使用
    class
    es表示可变类型
这些都是正确的方向吗


示例:假设我们有一个结构,它简单地包装了一个整数,该整数通过一个变异方法
next()
递增1

在某个初始值设定项中,我们像这样实例化

self.propertyWithLongAndAnnoyingName = Counter()
后来在同一范围内,忘记了这个属性包含一个struct,我们将它赋给一个简洁命名的局部变量
p
,并递增它


如果你的问题不仅仅是一个精心设计的巨魔,那么它看起来更像是心理问题而不是实际问题,因此可能不适合堆栈溢出:这是一个意见问题。然而,认真对待你,假设这个问题是真诚的,我可以根据经验告诉你,作为一个在Objective-C中编程多年并在Swift首次公开发布时学习Swift的人,结构等价值型对象的存在让我松了一口气,不像你想象的那样是个问题

<>在SWIFT中,一个结构是优秀的对象类型。当您只需要一个对象时,就可以使用struct。使用类是非常特殊的,仅限于以下情况:

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

  • 您需要一个超类/子类关系(在纯Swift编程中非常罕见;通常只有在使用Cocoa时才会发生这种情况)

  • 其目的是表示个人身份至关重要的外部现实(例如,UIView需要是引用类型,而不是值类型,因为界面中确实存在个人视图,我们需要能够区分它们)

  • 真正的易变性是一个要求

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

var p = self.propertyWithLongAndAnnoyingName
p.next()
…如果
self.propertywhithlongandannoyingname
也发生了变异,那将是一个彻底的打击。值类型表示一种安全形式。对
p
的赋值和
p
的变异将准确地将
自身属性与longandannoyingname
隔离开来

事实上,可可本身也竭尽全力防止这种事情发生。这就是为什么,例如,
propertyWithLongAndAnnoyingName
永远不会(从外面看)是一个NSMutableString的原因——它将是一个NSString,它在对象周围设置了一个不可变的围栏,即使它是一个引用类型。因此,在Swifts中使用structs解决了一个问题,Cocoa必须通过更复杂和(对许多初学者来说)更神秘的方法来解决这个问题

然而,这种情况在实际情况中相对较少,因为你的假设的另一个方面也是错误的:

我无法想象这个问题的答案仅仅是“请记住,您处理的是一个结构而不是一个类”,因为这非常容易出错,特别是因为类/结构对于使用任何特定数据结构的代码来说都是完全不透明的

我自己的经验是,在任何重要的情况下,我总是知道我是在处理一个结构还是一个类。简单地说,如果它是Swift库对象(字符串、数组、Int等),那么它就是一个结构。(库基本上没有定义任何类。)如果它是Cocoa对象(UIView、UIViewController),那么它就是一个类

事实上,命名约定通常对您有所帮助。地基覆盖是一个很好的例子。NSData是一个类;数据是一个结构

还要注意,不能将
let
引用变异为结构,但可以将
let
引用变异为类。因此,在实践中,你会很快体验到什么是什么,因为如果可以,你总是使用
let

最后,如果将一个对象交给第三方进行适当的变异对您来说真的很重要,那就是
inout
的作用。那样的话,你知道
var p = self.propertyWithLongAndAnnoyingName
d.next() // actually increments a copy
var p = self.propertyWithLongAndAnnoyingName
p.next()