2011-09-06 50 views
8

我有一個函數將另一個函數作爲參數。事情是這樣的:AS3傳遞函數作爲參數創建內存泄漏

public function onHits(target : Shape, callback : Function) : void 

我傳遞一個成員函數作爲每當通過目標命中的東西,應該被稱爲參數一起使用。該函數被多次調用一幀。所以用它做:

//code... 
CollisionManager.onHits(myShape, onHitCB); 
//code... 

的擊中功能:

public function onHitCB(hitObject : *) : void 
{ 
    //removed all code to test this problem 
} 

當我這樣做,我有內存泄漏。我已經將問題隔離到了onHits方法中,並將其他所有內容註釋掉了。 onHits是一個沒有代碼的空方法,onHitCB也是空的。如果我註釋掉對onHits的調用,則不存在內存泄漏,如果我傳遞null而不是onHitCB,則不會發生內存泄漏。

所以很明顯,當我通過HITCB作爲一個參數是問題。所以我認爲這可能是因爲Flash分配了一些內存來創建函數指針並且不釋放它,但是我在調​​試模式下調用System.gc()每一幀,並且泄漏仍然存在。這意味着這可能是SDK中的一個錯誤,或者我沒有做正確的事情。

我已經通過保持一個指向我在我的對象的構造函數分配功能的變量中發現一個奇怪的解決方法:

private var func : Function; 

public function MyObject() 
{ 
    func = onHitCB; 
} 

,這將清除內存泄漏即使我仍然可以通過onHitCB作爲參數。所以這意味着它不是獲取onHitCB的「getter」函數,而是導致內存泄漏的其他內容?

我很困惑。如何導致內存泄漏:

public function MyObject() 
{ 
} 

public function update() : void 
{ 
    CollisionManager.onHits(myShape, onHitCB);//empty function 
} 

public function onHitCB(hitObject : *) : void 
{ 
    //removed all code to test this problem 
} 

但不是這樣? :

private var func : Function; 
public function MyObject() 
{ 
    func = onHitCB; 
} 

public function update() : void 
{ 
    CollisionManager.onHits(myShape, onHitCB);//empty function 
} 

public function onHitCB(hitObject : *) : void 
{ 
    //removed all code to test this problem 
} 

有沒有辦法不必做這個解決方法?

+0

爲什麼不讓onHitCB成爲CollisionManager的類成員?聽起來像你的功能是放寬範圍。在onHits的最後一行嘗試callback = null; –

+0

嘗試在onHits結束時將回調設置爲null,但泄漏仍然存在。到目前爲止,保留對函數的本地引用是我能找到的唯一解決方法。 – Godfather

回答

5

[...]當你傳遞的方法作爲參數綁定方法會自動創建。綁定方法確保this關鍵字始終引用定義方法的對象或類。 Source

這聽起來像創建一個方法的引用不是使用一個簡單的getter。一個新方法的閉包對象是生成的。所以你的假設是對的。

我想知道爲什麼沒有爲每個實例緩存引用以及爲什麼它們不是垃圾收集。最好避免創建多個引用。只有一次引用方法正是我在多個地方使用該方法時所要做的,所以大多數情況下我不會將其稱爲解決方法,而是一種良好的DRY練習。在你的例子中它是有道理的,假設一個方法引用將使用一個簡單的getter。

+0

我看到,這證實了我對每次傳遞函數時創建的對象的懷疑。但是我仍然不明白爲什麼在傳遞函數(而不是本地引用)的同時保留本地引用不會再造成內存泄漏。 – Godfather

+0

我認爲它只是在*方法名*被直接使用時(在賦值或作爲參數使用時)纔會生成。因此'func = onHitCB;'只會產生一次方法閉包,使用'func'作爲參數不會導致它再次生成。 – Kapep

+0

是的,但我沒有使用func作爲參數,我仍然使用onHitCB(如果仔細觀察上面的兩個示例,它們都會使用onHitCB調用onHits),這對我來說是最大的神話。我現在最好的解釋是,方法閉包被緩存爲一個弱引用,通常會被垃圾收集器清除,但不會出於未知原因。由於它是一個弱引用,因此需要在每次調用時重新生成,但由於我在本地保留引用,因此它將保持足夠長的時間以在以後的調用中重用。 – Godfather

0

我不確定你的代碼是關於onHits函數,但是如果它不需要額外的時間在另一個週期完成。那麼我建議你這樣做:

static public function onHits(target : Shape) : * 
{ 
    // do what you need 

    // return the hitObject; 
    return hitObject; 
} 

public function update() : void 
{ 
    // parse the object direc to to the function. 
    onHitCB (CollisionManager.onHits(myShape)); 
} 

public function onHitCB(hitObject : *) : void 
{ 
    if (hitObject == null) 
     return; 

    // if it not null then do all your calculations. 
    //removed all code to test this problem 
} 
+0

目前,onHits函數什麼都不做。我已經從它中刪除了所有代碼,但只是通過傳遞一個Function指針來調用它會造成內存泄漏。我可以通過改變我的代碼的設計來避免這個問題,但只是傳遞函數指針不應該導致這個問題(>。<) – Godfather

1

有關在使用功能性技術時確切地發現內存泄漏的信息,請參閱http://www.developria.com/2010/12/functional-actionscript-part-1.html。另外,請注意,使用這種靜態方法是非常糟糕的做法(http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/),而您剛剛開始遇到使用這種技術造成的許多問題。這聽起來像是你在項目中足夠早,你並沒有完全致力於這條道路,所以你可能想看看其他方式來編程。

-1

這就是爲什麼我們在OOP風格編程中不這樣做的原因。
最好的辦法是正確地做,並將回調添加到CollisionManager類。
當你保留一個本地引用時,它可以被GCed的原因是因爲這個函數不會丟失範圍,因爲那個var在那裏持有引用。
一旦某件事情失去了範圍,GC幾乎是不可能的。

試試這個,看你如何失去範圍。

private var somevar:String = 'somevar with a string'; 
public function MyObject() 
{ 
} 

public function update() : void 
{ 
    CollisionManager.onHits(myShape, onHitCB);//empty function 
} 

public function onHitCB(hitObject : *) : void 
{ 
    trace(this.somevar) // scope should be lost at this point and somevar should be null or toss an error. 
} 
+0

這不應該工作嗎?我不知道你的意思是失去範圍(函數指針保持對它們所屬實例的引用,所以可以用正確的參數調用它們)。通過參數傳遞函數工作正常,跟蹤「somevar與字符串」像它應該(我認爲?)。我不明白爲什麼傳遞檢測到碰撞時必須調用的回調函數可能是「糟糕的設計」,每個對象都可能也可能應該以不同的方式處理碰撞。這是用在多種情況下(box2D這是因爲它的碰撞引擎我相信),因爲它更靈活。 – Godfather

+0

好的壞例子。如果我有時間,我會爲你更好地解釋我在談論的內容。 –