提问人:pseudosudo 提问时间:12/2/2018 更新时间:9/29/2023 访问量:315
意外更改结构的副本而不是结构本身
Accidentally mutating a copy of a struct instead of the struct itself
问:
多年来,我使用标识类型进行编程,我发现使用变异值类型非常有压力,因为经常存在意外分配(从而复制)新变量,然后改变该副本并期望看到这些更改反映在原始结构中的风险(最后给出的示例)。
我的实际问题是:有哪些方法/编码风格/实践可以防止这种事情发生?
我在想什么:我无法想象这个问题的答案只是“只要记住你处理的是结构而不是类”,因为这非常容易出错,特别是因为类性/结构性对于使用任何特定数据结构的代码都是完全不透明的。
我还看到很多关于使用“纯功能风格”的讨论,我完全赞成,除了:
- 在 mutating 方法还需要返回值的情况下(如下面的示例所示),这样做似乎非常尴尬。当然,我可以返回一个元组,但这不可能是 The Way。
(newStructInstance, returnValue)
- 我想,只要你想要突变,实例化结构的新副本就不可能有任何效率(不像重新分配结构,据我所知,它会创建一个新实例而不会产生任何成本)。
我的问题的一些可能答案可能是:
- 你的例子是人为的,这种事情在实践中很少发生,如果有的话
- 你使用的是迭代器(反)模式(见下面的代码),所以这就是你的原罪
- 放宽并仅将 S 用于不可变类型,将 es 用于可变类型
mutating
struct
class
这些都朝着正确的方向发展吗?
示例:假设我们有一个结构体,它简单地包装一个整数,它用一个 mutating 方法递增 1。next()
struct Counter {
var i: Int = 0
mutating func next() -> {
self.i += 1
return self.i
}
}
在某个地方的某个初始值设定项中,我们像这样实例化
self.propertyWithLongAndAnnoyingName = Counter()
后来在同一范围内,忘记了这个属性包含一个结构体,我们将其分配给简洁命名的局部变量并递增它。p
var p = self.propertyWithLongAndAnnoyingName
d.next() // actually increments a copy
答:
作为一个在 Objective-C 中编程多年,然后在 Swift 首次向公众发布时学习它的人,值类型对象(如结构体)的存在是一个巨大的解脱,而不是你似乎认为的问题。
在 Swift 中,结构体是卓越的对象类型。当你只需要一个对象时,你使用一个结构。使用类是非常特殊的,仅限于以下情况:
你使用的是 Cocoa(它是用 Objective-C 编写的,你只需要接受它的对象是类)
你需要一个超类/子类关系(在纯 Swift 编程中非常罕见;通常这种情况只是因为你使用的是 Cocoa)
其目的是表示外部现实,其中个人身份至关重要(例如,UIView 需要是引用类型,而不是值类型,因为界面中确实存在单独的视图,我们需要能够区分它们)
真正的可变性是必然的
因此,一般来说,大多数程序员的感觉与你假设的心理状态正好相反:当一个对象意外地变成一个引用类型(一个类)并且突变影响到一个远程引用时,他们会感到惊讶。在您提供的代码示例中:
var p = self.propertyWithLongAndAnnoyingName
p.next()
...如果也发生变异,那将是一个彻底的震惊。值类型表示安全的一种形式。的赋值和突变恰恰是为了防止被改变。self.propertyWithLongAndAnnoyingName
p
p
self.propertyWithLongAndAnnoyingName
事实上,可可本身会竭尽全力防止这种事情发生。这就是为什么,例如,模式永远不会是(从外部看到的)NSMutableString,而是一个NSString,它围绕对象设置了一个不可变的围栏,即使它是一个引用类型。因此,在 Swifts 中使用结构解决了一个问题,Cocoa 必须通过更复杂和(对许多初学者来说)神秘的措施来解决这个问题。propertyWithLongAndAnnoyingName
然而,像这样的情况在实际中相对罕见,因为你的假设的另一个方面也是错误的:
我无法想象这个问题的答案只是“只要记住你处理的是结构而不是类”,因为这非常容易出错,特别是因为类性/结构性对于使用任何特定数据结构的代码都是完全不透明的
我自己的经验是,在任何重要的情况下,我总是知道我是在处理结构还是类。简单地说,如果它是一个 Swift 库对象(String、Array、Int 等),它就是一个结构体。(该库基本上没有定义任何类。如果它是一个 Cocoa 对象(UIView、UIViewController),则它是一个类。
事实上,命名约定通常对您有所帮助。基金会叠加层就是一个很好的例子。NSData 是一个类;数据是一种结构。
另请注意,您不能改变对结构的引用,但可以改变对类的引用。因此,在实践中,您很快就会体验到哪个是哪个,因为如果可以的话,您总是使用。let
let
let
最后,如果将一个对象交给某个第三方进行就地突变对你来说真的很重要,那就是目的。在这种情况下,您知道这一点,因为您必须显式传递引用(地址)。如果你传递给的参数不是 ,你会假设你的原始对象是安全的,不会发生突变,你会为此感到高兴。inout
inout
上一个:Rust 中的二维向量
评论