製作大量產生遊戲特效的畫面時,總會遇到畫面突然一頓一頓的時刻。
如果持續用init建立像子彈或粒子這種生命週期很短、很快消失的物件,就會在 Instruments 中看到記憶體圖表像鋸齒一樣跳動。
這時就輪到Swift 物件池模式登場了。
先說結論:物件池是一種「不每次建立新物件,而是預先建立後借用並歸還」的再利用技術,目的本身就和容易混淆的 Flyweight 不同。
本文會依序說明物件池是什麼、和 Flyweight 有何不同,以及如何用 Swift 實際實作。
什麼是物件池模式?
物件池會預先建立多個建立成本高的物件,並將它們放進「池」中。
需要時不是重新建立,而是從池中取出使用,使用完再歸還。
核心就是再利用。目的是降低反覆進行init與deinit所產生的記憶體配置與釋放成本。
物件池的一句話總結是:「不要建立,借來使用再歸還。」
它主要適合以下情境。
- 像子彈、粒子一樣在短時間內大量建立與銷毀的物件
- 像網路連線或執行緒一樣,建立本身成本很高的資源
- 像
UITableViewCell的再利用佇列一樣,在畫面上持續替換的 View
其實 iOS 開發者早就使用過物件池了。dequeueReusableCell就是 Apple 建立的物件池。
和 Flyweight 有什麼不同?
兩者都給人「節省使用物件」的印象,因此很容易混淆。
但它們的目的完全不同。
物件池是讓像子彈這類物件錯開時間輪流使用的方式。現在這顆子彈使用完並歸還後,下一顆子彈就會接替它的位置。
Flyweight 是讓多個地方同時參考同一個共用物件的方式。例如繪製森林中的 1 萬棵樹時,顏色、材質等共用資料只保留一份,位置值則另外傳入。
整理成表格後,可以分成以下幾點。
| 分類 | 物件池 | Flyweight |
|---|---|---|
| 目的 | 降低建立與釋放成本 | 降低記憶體使用量 |
| 再利用方式 | 借用並歸還(時間分割) | 同時共用(空間分割) |
| 狀態 | 每個物件維持獨有狀態 | 分離共用狀態與外部狀態 |
| 代表範例 | Cell 再利用、粒子 | 字型字形、地圖圖示 |
一句話總結:物件池是「輪流再利用」,Flyweight 是「大家一起共用」。
如何用 Swift 建立物件池
結構比想像中簡單:一個存放可用物件的陣列,再加上借出與歸還的兩個方法即可。
以下是用泛型撰寫的簡單物件池。
final class ObjectPool<T> {
private var available: [T] = []
private let factory: () -> T
init(factory: @escaping () -> T) { self.factory = factory }
func acquire() -> T { available.popLast() ?? factory() } // 沒有就新建
func release(_ item: T) { available.append(item) } // 使用完就歸還
}
acquire()會在池中還有物件時取出,只有池子空了才新建。
透過release()歸還後,下一次請求就會再利用該物件。
實際套用到粒子時,流程會像這樣。
let pool = ObjectPool<Particle> { Particle() }
let p = pool.acquire() // 從池中借用
p.reset(at: point) // 初始化狀態很重要!
// ...在畫面上使用完畢後
pool.release(p) // 歸還
這裡有一點一定要記住:歸還的物件仍然保留先前的狀態。
因此再次借出時,必須經過像reset()這樣的初始化,才能避免幽靈資料。
什麼時候該用、什麼時候該避免物件池?
覺得好用就到處使用,反而可能造成反效果。
這是我實際使用後整理出的判斷標準。
建議在這些情況使用。
- 每秒反覆建立與銷毀數十個以上物件時
- 建立單一物件的成本明顯很高時
- 記憶體圖表像鋸齒一樣跳動,並且看得出 GC(垃圾回收)/ARC(Automatic Reference Counting,自動參照計數)的負擔時
這些情況請先重新考慮。
- 如果只是偶爾建立一兩個輕量物件,管理物件池的成本反而更高
- 如果忘了歸還,池子會被用空,反而每次都必須重新建立
- 在多執行緒環境中,存取物件池需要鎖定或佇列同步
Swift 有很多值型別(struct),ARC 也相當有效率,因此不必無條件優先導入物件池。
建議先用 Instruments 測量,確認瓶頸後再導入。
物件池是節省建立成本的再利用模式,而 Flyweight 是節省記憶體的共用模式。
只要確實掌握兩者的差異,就能依情境選擇合適的方法。
先測量,再在需要時精準導入。希望你也能親身體驗畫面幀率變得更加流暢的時刻。

