[Unity] 關於 Component 的 GC 測試,出現了大問題! – Testing GC of Component in Unity
不久前才測試完了 Delegate 的 GC,雖然只是驗證了一個可以預期的結果。不過才過幾小時,我就想到類似的議題在 Unity Component 上,是否會因為 Component 特性而產生不太一樣的結果?
雖然本是要測試 Delegate,不過我同時也想驗證一下之前就發現的一個 Component 特性:自行移除相關參照 (Reference)。
結果竟然在測試過程中有了額外發現,間接造成 Delegate 的測試無法進行下去… 所以文章便直接停止在 Unity Component 的 GC 測試。
測試用 Component
為了驗證 GC,必須先實作出一個測試用的單元:
1 | using System.Collections; |
在 C# 的 GC 機制下,類別的解構子 (Destructor) 會在被 GC 的瞬間執行,所以透過解構子的可以確定 GC 的執行狀況,這點在一般情況下是完全說得通的。
但因為 Component 在 Unity 之中有著另外一套獨有的生命週期,所以實際開發之中是絕對不建議在 Component 之中實作建構子 (Constructor) 以及解構子 (Destructor) 的,這邊是測試需要才特別如此應用。
測試用腳本
1 | using System.Collections; |
測試結果
這邊測試結果的圖片,紅線以上的部分是 ReferenceTest.Start() 所執行的部分,紅線以下是另外使用按鈕呼叫 ReferenceTest.CheckReference() 的結果。
在前 3 行中,ComponentA 還沒有實體,所以 ComponentA.Count 為 0。
在 4 到 6 行間,已經執行了 AddComponent 產生實體,同時參照到 referenceA、referenceB 兩個變數上進行儲存,此時 ComponentA.Count 為 1。
在 7 到 9 行間,已經執行了 Destroy 的動作來移除 ComponentA,而 refernceA、referenceB 兩個變數的參照依舊,並沒有被設為 null,此時 ComponentA.Count 為 1。
到此為止,一切的運作都跟一般類別沒甚麼不同,Destroy() 這個方法會將場景中的 ComponentA 移除,但並沒有自動將 refernceA 設為 null,referenceB 沒有參與動作,所以也保持著參照而沒有設為 null。所以為了正確執行 GC,似乎得手動將 refernceA、referenceB 這兩個變數手動設為 null?
… (請停頓消化一下)
不,我們不另外進行手動設值為 null,在等待幾個 frame 之後再次執行 CheckReference() 檢查 refernceA、referenceB 這兩個變數。
明明甚麼事都沒做,refernceA、referenceB 兩個變數自己就變成 null 了!我所能提出來的假設,就是 Unity Component (MonoBehaviour) 的生命週期中,運作完 OnDestroy 等事件之後,會在生命週期的最後透過 Unity 本身的某種機制將所有關聯的參照都自動設為 null,來避免開發者無意間使用了已經失效的 Component。
這不是又神奇又方便嗎!以上便是我在無意間發現的一個 Component 特性。
… (請再停頓消化一下)
等等,此時 ComponentA.Count 依舊為 1?說好的沒有參照就會被 GC 呢?
大問題
在上一段落的最後,又發現了 Unity 似乎有著不明的機制干擾著 .Net 進行 GC 的動作, 因此 Delegate 的 GC 測試無法進行下去。
為了瞭解這問題的細節,我在網路上尋找了相關問題的討論,但是暫時沒有確切結果。
- 參考連接 1 提到,Object.Destory 到 Despose 之間 Unity 做了一些神奇的事情。
- 參考連結 2 提到,只在 Editor 環境下,Unity 會用設值為 null 來代替實際 Dispose (Why did you do this?),而在輸出專案後,便會正常 GC (在測試驗證前我持保留態度)。
- 參考連結 3 提到了 Object.DestroyImmediate() 這個方法,經過測試確認,它可以在執行後立刻自行移除相關參照,但不建議使用,且同樣不會引起 GC。
未來有機會我想解決兩個疑問:
- 自行移除相關參照的特性能否在輸出專案之後繼續成立,能否實際應用於開發上?
- Unity 的某個機制會影響 Component 的 GC,是否輸出專案之後就真的沒有問題?還是依然有特定的地方要注意?