Swift 与 Objective-C

Swift Copy-on-Write:值类型替代原型模式的方式(总结)

Swift Copy-on-Write 在赋值值类型时共享存储空间,只有一方被修改时才执行实际复制。本篇将说明这一优化如何保留值语义,同时降低原型模式的作用。

4 分钟阅读
Swift Copy-on-Write:值类型替代原型模式的方式(总结) 封面图

如果你先学习 Objective-C 或 Java,再转向 Swift,一定会遇到一个容易卡住的地方。

那就是“如何复制对象?”这个问题。

你很容易到处寻找类似 clone() 的方法,但在 Swift 中几乎不需要这样做。

先说结论。

得益于结构体、数组等值类型,以及 Copy-on-Write(延迟复制)优化,Swift 在语言层面替代了设计模式中的“原型模式”。

无需为复制对象额外设计模式,只要赋值,就能得到安全的副本。

本文将结合示例,整理原型模式原本要解决的问题,以及 Swift 的值类型和 Copy-on-Write 如何自然地承担这一职责。

要区分复制类型,首先要以 浅拷贝 vs 深拷贝 为基础。如果还需要复制类,Swift 原型模式与 NSCopying 展示了另一种选择。


原型模式原本要解决的问题

原型模式属于 GoF 设计模式中的创建型模式。

核心很简单:不从头创建新对象,而是复制已有对象来获得新实例。

为什么需要这种方式?

在 Objective-C 和 Java 等语言中,对象大多是引用类型。

将对象赋值给变量时,复制的不是值,而是指向同一对象的地址。

因此,修改 A 时 B 也会跟着改变。

为了避免这种情况,开发者会自行创建 clone()copy() 方法,让它返回“真正的副本”。

原型模式就是应对这种情况的工具。

  • 对象创建成本高,但需要多个相似对象时
  • 需要不修改原对象的独立副本时
  • 希望由对象自身负责复制逻辑时

Swift 值类型:赋值即可完成复制

Swift 的结构体(struct)和枚举(enum),以及 Array、Dictionary、String 等标准类型,全部都是值类型。

值类型在赋值或传入函数时就会被复制。

换句话说,语言会自动为你创建副本。

看示例会清楚得多。

struct Point { var x: Int; var y: Int }

var a = Point(x: 1, y: 2)
var b = a      // 此时值会被复制
b.x = 99
// a.x仍然只 1, b.x 99

b = a这一行就替代了原型模式中clone()所承担的工作。

不需要额外的复制方法,也不需要采用复制协议。

原始的 a 会受到安全保护,而 b 会成为完全独立的副本。

对象相互纠缠所导致的棘手 bug,从结构上就不可能发生。


Copy-on-Write 解决性能问题的方式

这里自然会产生一个疑问。

“每次赋值都完整复制的话,大数组不会变得太慢吗?”

这个担忧很合理。因此 Swift 使用的优化就是 Copy-on-Write,简称 CoW。

CoW 的原理如下。

赋值时先共享内部存储空间,实际数据复制则延后进行。

当两者中的任意一方尝试修改值时,才会真正执行复制。

修改前共享,修改瞬间分开
修改前共享,修改瞬间分开
时机 内部操作 成本
赋值时 共享存储空间(仅引用) 非常低
仅读取时 持续保持共享 无需复制
修改值时 此时才实际复制 仅在此时发生

因此,开发者既能享受值类型的安全性,也无需承担不必要的复制成本。

如果只读取而不修改,复制根本不会发生。

理解这个概念后,我对复制的烦恼少了很多
理解这个概念后,我对复制的烦恼少了很多

Array、Dictionary、Set、String 等标准库类型已经内置了 CoW。

无需自行实现,语言和标准库会自动处理。


那么,还有需要使用原型模式的情况吗?

“所以在 Swift 中,原型模式已经完全过时了吗?”

也不一定。

有些情况下必须使用类(class),例如需要引用语义、需要与 Objective-C 互操作,或需要继承。

这时如果需要类实例的独立副本,仍然必须自行实现复制逻辑。

Swift 提供了 NSCopying 协议和 copy(),这基本上就是原型模式的传统形式。

可以这样区分,会更容易理解。

  • 使用结构体和值类型 → 仅通过赋值即可完成复制,无需原型模式
  • 必须使用类 → 必要时自行实现复制逻辑,模式在这里继续存在

因此,Swift 社区经常建议“优先考虑值类型”。

因为以值类型为默认选择,复制问题本身就消失了。


总结

如果强行把陌生语言中的设计模式搬过来,代码反而常常会变得复杂。

只要记住,在 Swift 中值类型和 Copy-on-Write 自然填补了原型模式的位置,复制相关的烦恼就会轻松许多。

下次需要复制对象时,在寻找 clone() 之前,先想想“要不要把它改成结构体?”一定会有所帮助。

延伸阅读