在Swift中,值类型(struct、enum、tuple)具有所谓的值语义:在传递或赋值给变量时,会复制所有内容 – 创建一个独立的实例。这可以避免与引用类型(class)相关的共享状态的复杂性。
然而,为了优化内存,集合(例如Array、Dictionary、Set)使用了copy-on-write策略:只有在其中一个实例被修改时才会进行复制。
示例:
var a = [1, 2, 3] var b = a b.append(4) print(a) // [1, 2, 3] print(b) // [1, 2, 3, 4]
在这里,数组 a 不会改变 — 尽管最初有共享的存储,但在 b 更改时,Swift会单独复制数据。
重要的是要记住:如果结构体包含引用类型(例如类),那么值语义仅适用于结构体本身,而不适用于嵌套的引用对象。
如果我们将数组传递给函数并在该函数内部编辑它,数组的内容会改变吗?解释struct和class的行为差异。
带示例的答案:
func mutate(_ arr: inout [Int]) { arr.append(100) } var source = [1, 2] mutate(&source) print(source) // [1, 2, 100]
如果不通过 inout 传递,第一次在函数内部进行修改时会自动复制,源数组不会改变。对于类,复制不会发生 – 原始对象总是会改变。
故事
开发者将引用对象放入结构体数组中,期望通过一个结构体的修改不会影响其他实例。实际上,在一个地方更改引用对象时,它们会意外地在所有地方同时更改(共享状态)。
故事
在团队项目中,试图通过每次访问时复制集合来防止竞争条件。这导致了内存开销过大和在处理大数组时性能下降。
故事
一名年轻开发者试图跟踪数组的变化,因此同时将其通过inout传递给多个处理函数。修改的顺序变得不明确,这导致了线程不安全的变化、bug和同步错误。