閱讀796 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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

  上一篇:go 第一個Hello world(1)
  下一篇:go python中動態加載模塊和類方法實現