使用身份类型编程已有多年,我发现使用变异值类型非常困难,因为存在不断偶然地将变量赋值(并复制)到新变量然后变异该 copy 的风险。希望看到那些更改反映在原始结构中(最后给出示例)。
我的实际问题:有什么方法/编码样式/做法可以防止此类情况的发生?
我在想什么:我无法想象这个问题的答案仅仅是“只记得您正在处理一个结构而不是一个类”,因为这极容易出错尤其是因为class-ness / struct-ness对于使用任何特定数据结构的代码完全不透明。
我也看到了很多有关使用“ purely functional style”的讨论,除了:
(newStructInstance, returnValue)
这样的元组,但是那不可能是The Way。我的问题的一些可能答案可能是:
mutating
,仅将struct
s用于不可变类型,将class
es用于可变类型这是否朝着正确的方向?
示例:假设我们有一个结构,该结构简单地包装了一个整数,该整数使用突变方法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
答案 0 :(得分:3)
您的问题,如果它不仅仅是一个精心设计的巨魔,似乎比实际更具有心理性,因此这可能不适合Stack Overflow:这是一个见解。但是,认真对待您并假设问题是真诚的,作为经验,我可以告诉您,作为一个在Objective-C进行了多年编程然后在首次向公众发布Swift时就学到了东西的人,诸如结构之类的值类型对象是一个巨大的救济,而不是像您所假定的那样存在问题。
在Swift中,结构是 par Excellence 对象类型。当您只需要一个对象时,就可以使用结构。使用类是非常例外的,仅限于以下情况:
您正在使用Cocoa(它是用Objective-C编写的,您只需要接受其对象是类)即可。
您需要一个超类/子类关系(在纯Swift编程中很少见;通常这种情况仅是因为您使用的是可可粉)
该对象用于表示个人身份至关重要的外部现实(例如,UIView必须是引用类型,而不是值类型,因为接口中确实存在单独的视图,因此我们需要能够区分它们)
真正的可变性是我们的迫切需求
因此,通常,大多数程序员会感到与您所假设的心态恰恰相反:当对象意外地成为引用类型(类)并且变异影响远程引用时,他们会感到惊讶。在您提供的代码示例中:
var p = self.propertyWithLongAndAnnoyingName
p.next()
...如果self.propertyWithLongAndAnnoyingName
也被突变,将是完全震惊。值类型表示安全的一种形式。准确地将p
的分配和p
的变异更改为绝缘 self.propertyWithLongAndAnnoyingName
。
事实上,可可本身竭尽全力保护反对这类事情。例如,这就是为什么模式是propertyWithLongAndAnnoyingName
永远不会(从外面看)是NSMutableString -它将是NSString,即使它是对象,它也围绕对象建立了不变性的篱笆。参考类型。因此,在Swifts中使用结构解决了这个问题,可可必须通过更为详尽的(对许多初学者而言)神秘的方法来解决。
但是,由于您的假设的另一个方面也是错误的,因此这种情况在实际情况中相对很少见:
我无法想象这个问题的答案仅仅是“只记得您正在处理一个结构而不是一个类”,因为这极容易出错,尤其是因为class-ness / struct-ness完全不透明使用任何特定数据结构的代码
我自己的经验是,在任何重要的情况下,我总是知道我是在处理结构还是在处理类。简单来说,如果它是一个Swift库对象(字符串,数组,整数等),则它是一个结构。 (该库基本上根本没有定义任何类。)如果它是可可对象(UIView,UIViewController),则它是一个类。
实际上,命名约定通常可以为您提供帮助。基础叠加层就是一个很好的例子。 NSData是一个类;数据是一个结构。
也请注意,您不能将一个结构的引用let
突变,但是您将可以将对类的let
引用突变。因此,在实践中,您会很快体验到哪一个,因为如果可以的话,您总是使用let
。
最后,如果将一个对象交给某个第三方进行就地突变真的很重要,那就是inout
的目的。在这种情况下,您就知道了,因为您必须显式地传递引用(地址)。如果您要传递的参数是 not inout
,则您将假定原始对象是安全的,并且不会发生突变。