开发应用时,有时需要创建数千甚至数万个相似对象。典型例子是在地图上放置数万个标记;在这种情况下,内存图很容易持续攀升。
这时可以考虑 Flyweight Pattern。简单来说,就是合并并共享多个对象共同使用的数据,从而大幅降低内存占用。不要每次都重新创建相同的东西,而是创建一次后共同使用。
本文将介绍 Swift 中的 Flyweight Pattern、适用场景,以及如何通过实际代码实现它。
Flyweight Pattern 到底是什么?
Flyweight 的意思是“轻量对象”,也是拳击中最轻量级别的名称。
这个想法很简单:将对象持有的数据分成两类。
- 内在状态(intrinsic):多个对象可以共享且不会变化的数据,例如树的种类、纹理和颜色。
- 外在状态(extrinsic):对象之间各不相同、会随时变化的数据,例如树的位置或大小。
想象一下要绘制一片森林。如果要画 100 万棵树,但它们实际上只有松树、橡树和枫树三种,该怎么办?
只需创建并共享三份纹理和颜色信息(内在状态),再单独保存 100 万个位置值(外在状态)。
重量级的公共数据只创建一份并共享,单独管理轻量的个体数据。这就是 Flyweight 的全部内容。
用 Swift 代码实现看看
只看文字不太容易理解,我们直接来看代码,把刚才的树木示例原样实现出来。
首先是用于存储要共享的内在状态的 Flyweight 对象。
// 共享的重量级数据(内在状态)
final class TreeType {
let name: String
let color: String
let texture: Data // 大容量纹理
init(name: String, color: String, texture: Data) {
self.name = name
self.color = color
self.texture = texture
}
}
接下来创建一个管理并共享这些 TreeType 的工厂。如果某种类型已经创建,就不再新建,而是直接复用。
// 相同类型不再新建,直接复用
final class TreeFactory {
private var pool: [String: TreeType] = [:]
func treeType(name: String, color: String, texture: Data) -> TreeType {
let key = "\(name)-\(color)"
if let existing = pool[key] { return existing }
let type = TreeType(name: name, color: color, texture: texture)
pool[key] = type
return type
}
}
现在,实际的树只保存位置等外在状态,重量级数据则通过 TreeType 进行引用。
// 每棵树只保存位置,类型通过引用共享
struct Tree {
let x: Double
let y: Double
let type: TreeType // 共享对象引用
}
即使种下 100 万棵树,TreeType 实例也只会有与种类数量相同的数量(例如 3 个)。其余全部只是指向这 3 个实例的引用。
什么时候该用,什么时候不该用?
Flyweight 并不总是正确答案。很多时候,不使用它反而更好。下面按场景进行总结。
| 场景 | Flyweight |
|---|---|
| 创建大量相同性质的对象 | 适合 |
| 对象拥有可以共享的重量级数据 | 适合 |
| 对象数量少 | 不必要(反而只会增加复杂度) |
| 大部分状态都因对象而异 | 效果有限 |
当大量对象和可共享的重量级数据这两个条件同时存在时,正是 Flyweight 发挥作用的场景。
在 Swift 中,class 作为引用类型本来就通过引用共享,因此当值类型 struct 整体包含重量级数据并被频繁复制时,效果尤其明显。
常见问题
Q. 它和 Singleton Pattern 有什么区别?
Singleton 强制类只能拥有一个实例。Flyweight 则允许存在多个实例,但只共享可以共享的部分。例如树的种类有 3 种,而树本身有 100 万棵。
Q. 我总是把它和缓存搞混。
目的不同。缓存是因为“不想再次计算或查询”而保存结果,Flyweight 则是为了“节省内存”而管理共享对象。不过,工厂的 pool 确实会像缓存一样工作。
Q. Swift Standard Library 中也使用它吗?
是的。字符串驻留和小整数缓存等优化都采用了类似的思路:不反复创建相同的值,而是将其共享。
Flyweight Pattern 这个名字听起来很复杂,但想法其实非常简单:“重复的重量级数据只创建一份,大家一起使用。”
如果需要大量处理相似对象,例如地图标记、粒子、Tile Map 或文本渲染,不妨考虑一下。你会发现内存图明显变得平稳。

