開發 App 時,有時需要建立數千甚至數萬個相似物件。最典型的例子是在地圖上撒出數萬個標記;在這種情況下,記憶體圖表很容易持續攀升。
這時就可以考慮 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 或文字算繪,不妨考慮一下。你會感受到記憶體圖表明顯變得平穩。

