JSB內存管理
原文地址:https://www.cocos2d-x.org/wiki/Memory_Management_of_JSB by u0u0
翻譯:晉文格墨
JSB的內存管理
基於Cocos2d-x 2.15,但同樣適用於Cocos2d-x 3.0。
JSB對象的生命周期
總所周知,javascript有自己的內存管理機製,即垃圾回收。Cocos2d-x模擬垃圾回收係統來管理Cocos對象。但這裏有一個問題,就是將Cocos2d-x對象綁定到javascript對象時由誰負責內存管理。
先看一個案例。
通過XXX.create()來分配對象內存
下麵的代碼分配了一個全局變量。
gnode = cc.Node.create();
gnode並沒有通過addChild()添加到其它cc.Node中。
在菜單項的回調函數中添加如下代碼:
// menuItem callback onButton:function (sender) { sender.addChild(gnode); }
當點擊此按鈕,你將會看到如下錯誤信息:
Cocos2d: jsb: ERROR: File /Users/u0u0/Documents/project/SK_parkour/scripting/javascript/bindings/generated/jsb_cocos2dx_auto.cpp: Line: 3010, Function: js_cocos2dx_CCNode_addChild Cocos2d: Invalid Native Object
發生了什麼!“Invalid Native Object”是什麼意思?
在javascript中gnode是一個全局變量,意味著它不能被回收。
但是gnode裏的CCNode會被Cocos2d-x回收。
為了弄清楚這個問題,你需要了解一下spidermonkey並深入研究javascript的綁定代碼。
cc.Node.create()的內部實現
詳細的實現代碼如下:
static JSFunctionSpec st_funcs[] = { JS_FN("create", js_cocos2dx_CCNode_create, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FS_END }; jsb_CCNode_prototype = JS_InitClass( cx, global, NULL, // parent proto jsb_CCNode_class, js_cocos2dx_CCNode_constructor, 0, // constructor properties, funcs, NULL, // no static properties st_funcs);
cc.Node.create()被對應的C函數是js_cocos2dx_CCNode_create(),如下:
JSBool js_cocos2dx_CCNode_create(JSContext *cx, uint32_t argc, jsval *vp) { if (argc == 0) { cocos2d::CCNode* ret = cocos2d::CCNode::create(); jsval jsret; do { if (ret) { js_proxy_t *proxy = js_get_or_create_proxy(cx, ret); jsret = OBJECT_TO_JSVAL(proxy->obj); } else { jsret = JSVAL_NULL; } } while (0); JS_SET_RVAL(cx, vp, jsret); return JS_TRUE; } JS_ReportError(cx, "wrong number of arguments"); return JS_FALSE; }
通過cocos2d:CCNode::create()成功分配的對象將會被封裝成js_get_or_create_proxy()創建的一個新對象js_proxy_t。
在js_get_or_create_proxy()函數裏,隻要關注下麵這行代碼:
JS_AddObjectRoot(cx, &proxy->obj);
這段代碼將一個JSObject添加到垃圾回收器的根集合中的spidermonkey api。proxy->obj是對應javascript裏的一個JSObject。
所以通過cc.Node.create()分配的對象將會一直保留在內存中,直到調用JS_RemoveObjectRoot()。
但是cocos2d::CCNode::create()是一個自動釋放對象,它會在下一個遊戲幀被Cocos2d-x回收。
CCobject的析構函數將會被調用,請注意下麵的代碼:
// if the object is referenced by Lua engine, remove it if (m_nLuaID) { CCScriptEngineManager::sharedManager()->getScriptEngine()->removeScriptObjectByCCObject(this); } else { CCScriptEngineProtocol* pEngine = CCScriptEngineManager::sharedManager()->getScriptEngine(); if (pEngine != NULL && pEngine->getScriptType() == kScriptTypeJavascript) { pEngine->removeScriptObjectByCCObject(this); } }
pEngine->removeScriptObjectByCCObject 做了一件神奇的事情。
void ScriptingCore::removeScriptObjectByCCObject(CCObject* pObj) { js_proxy_t* nproxy; js_proxy_t* jsproxy; void *ptr = (void*)pObj; nproxy = jsb_get_native_proxy(ptr); if (nproxy) { JSContext *cx = ScriptingCore::getInstance()->getGlobalContext(); jsproxy = jsb_get_js_proxy(nproxy->obj); JS_RemoveObjectRoot(cx, &jsproxy->obj); jsb_remove_proxy(nproxy, jsproxy); } }
JS_RemoveObjectRoot函數將JSobject從javascript根集合中移除。 jsb_remove_proxy將proxy(委托)從hash表中移除。
現在我們可以解釋本文開始提出的問題了。
Cocos2d-x的垃圾回收係統負責內存管理
回到gnode,它是一個全局變量。CCObject的析構函數JS_RemoveObjectRoot的作用隻是平衡JS_AddObjectRoot的創建。Spidermonkey將不會回收這個全局變量,但是gnode的本地對象將會被釋放。訪問gnode的本地對象將會產生之前看到的那個錯誤。
通過new分配對象
思考一下下麵的代碼:
gnode=new cc.Node;
為了找到正確答案,同樣需要深入研究JSB代碼。
如之前提到的,cc.Node的構造函數是js_cocos2dx_CCNode_constructor()。
請注意下麵的代碼:
if (argc == 0) { cocos2d::CCNode* cobj = new cocos2d::CCNode(); cocos2d::CCObject *_ccobj = dynamic_cast(cobj); if (_ccobj) { _ccobj->autorelease(); }
本地對象被壓入到Cocos2d-x的自動釋放池中。所以new和create()是一樣的。
關於retain()和release()
有兩個函數可以用於手動控製對象的生命周期。如果你想避免之前例子中產生的錯誤。
你可以有以下兩個選擇:
1.將gnode添加到其它的CCNode中,addChild()將gnode保留在內部。
2.在create()之後立即調用gnode.retain()。
在第二種情況下,你需要在合適的時候調用gnode.release()以防止內存泄漏。下一節將會介紹它。
ctor()和onExit()
Cocos2d-x JSB使用Simple JavaScript Inheritance By John Resig。但是構造函數的名字不一樣。
在JSB中,ctor是構造函數。對應的onExit則扮演的是析構函數,它會在CCNode釋放之前被調用。
下麵的例子演示了如何手動控製JSB對象的生命周期。
var container = cc.Node.extend({ ctor:function () { this._super(); this.gnode = cc.Node.create(); this.gnode.retain(); }, onExit:function() { this.gnode.release(); this._super(); }, });
最後更新:2017-04-03 12:56:05