Lua弱引用表

今天来介绍一个lua里面的新机制:弱引用表(weak table)。

起因

当时在找内存泄漏的解决方案的时候了解到了这个lua机制。的确它在某些内存泄漏问题中可以发挥强大的作用,但是在项目中不太能单纯用这个机制解决现有的问题(组合类没有绑定luabehaviour,且在其gameobject被destroy掉时因没有触发某绑定了luatable的按钮的生命周期ondestroy事件导致c#侧持有lua侧userdata而无法释放)。

机制

lua的垃圾回收

首先明确一点:Lua语言自身有垃圾回收机制。当lua侧某个变量没有再被其他地方引用到的时候,它将会被自动回收。比如下面一段代码:

A = Class.class("A")
A.b = nil
B = Class.class("B")
a = A.new()
a.b = B.new()
a.b.c = 1
a.num = 1
collectgarbage()
print(a.num) --print:1
print(a.b.c) --print:1
a = nil

这里由a来创建一个B对象b,在我们中途去干一些操作的时候这个b并不会被消失,因为lua内部记录了这个对象引用数为1,不为0的对象它是不敢乱动的。
而后a被赋值为nil的时候我们不需要手动设置b为nil,因为已然没有其他地方持有这个对象了,即引用数为0,所以被当作垃圾清理掉了。
注意:引用概念只存在于tables、userdata、functions。

Weak table:不增加引用数

lua为我们提供了一种手段,在创建弱引用表的时候,内部的某些被引用到的对象其引用数并不增加。如上方的a如果是弱引用表并设置value为弱引用的话,那么在回收之后想要访问到这个表内引用的对象是无法行得通的(只有有引用概念的类型才受到影响)。

A = Class.class("A")
A.__mode = "kv"
A.b = nil
B = Class.class("B")
a = A.new()
a.b = B.new()
a.b.c = 1
a.num = 1
print(a.num) --print:1
print(a.b.c) --print:1
collectgarbage()
print(a.num) --print:1
print(a.b.c) --error:attempt to index a nil value (field 'b')
a = nil
同样的,function也会被回收:
A = Class.class("A")
A.__mode = "kv"
a = A.new()
function a:Test()
    print("test")
end

a:Test() --print:test
collectgarbage()
a:Test() --error:method 'Test' is not callable (a nil value)
a = nil

存在价值

为什么有了垃圾回收机制,还要有这个不增加引用数的weak table机制?
lua文档这样解释的:

垃圾收集器只能在确认对象失效之后才会进行收集;它是不会知道你对垃圾的定义的。一个典型的例子就是堆栈:有一个数组和指向栈顶的索引构成。你知道这个数组中有效的只是在顶端的那一部分,但Lua不那么认为。如果你通过简单的出栈操作提取一个数组元素,那么数组对象的其他部分对Lua来说仍然是有效的。同样的,任何在全局变量中声明的对象,都不是Lua认为的垃圾,即使你的程序中根本没有用到他们。这两种情况下,你应当自己处理它(你的程序),为这种对象赋nil值,防止他们锁住其他的空闲对象。

用法

表的weak性由他的metatable的__mode域来指定的。在这个域存在的时候,必须是个字符串:如果这个字符串包含小写字母‘k’,这个table中的keys就是weak的;如果这个字符串包含小写字母‘v’,这个table中的vaules就是weak的。
设置成k:

key = {name = "key"}
test = {}
test[key] = 1
for k, v in pairs(test) do
    print(v) --print:1
end
key = nil
collectgarbage()
for k, v in pairs(test) do
    print(v) --print:1
end
key = {name = "key"}
test = {}
setmetatable(test,{__mode = "k"})
test[key] = 1
for k, v in pairs(test) do
    print(v) --print:1
end
key = nil
collectgarbage()
for k, v in pairs(test) do
    print(v) -- no print
end

设置成v:

key = {name = "key"}
test = {}
test[1] = key
for k, v in pairs(test) do
    print(v) --print:table: 0000020DC15B2B30
end
key = nil
collectgarbage()
for k, v in pairs(test) do
    print(v) --print:table: 0000020DC15B2B30
end
key = {name = "key"}
test = {}
setmetatable(test,{__mode = "v"})
test[1] = key
for k, v in pairs(test) do
    print(v) --print:table: 0000020DC15B2B30
end
key = nil
collectgarbage()
for k, v in pairs(test) do
    print(v) --no print
end

同样对function适用:

key = {name = "key"}
test = {}
setmetatable(test,{__mode = "v"})
function test.func()
    print("test.func")
end
test.func() --print:test.func
collectgarbage()
test.func() --error:field 'func' is not callable (a nil value)

使用场景

记忆函数

如果是经常需要使用的一些global table,就可以使用记忆函数来优化存储空间:
local spriteAtlas = LuaAsset.LoadSpriteAtlas(“TestAB/Atlas/UI”)
imgTest.sprite = spriteAtlasProp:GetSprite(“test”)
之前已经说过,如果要走一遍wrap的装箱拆箱操作,这种是非常耗时的,所以我们可以利用弱引用来进行spriteAtlas的存储:

spriteAtlas = {}
setmetatable(spriteAtlas, {__mode = "v"})
function spriteAtlas.LoadSpriteAtlas(path)
    if spriteAtlas[path] == nil then
        spriteAtlas[path] = LuaAsset.LoadSpriteAtlas(path)
    end
    return spriteAtlas[path]
end

function spriteAtlas.GetSprite(atlasName,key)
    if spriteAtlas[key] == nil then
        spriteAtlas[key] = spriteAtlas.LoadSpriteAtlas(atlasName):GetSprite(key)
    end
    return spriteAtlas[key]
end

我们可以将同样的sprite结果存储在同一个table中,这样做可以省下很长时间的加载。还有一个有趣的结果就是,当我们不再使用那些不频繁的sprite(比如只会用到一次的sprite,之后再存储它就完全没有必要)的时候,这个table会直接清理掉而非等到这个进程完全结束,因为垃圾收集器一次次的在清理这个table。然而,只要某个key值对应的sprite正在被使用,它就不会从table中被移除。所以,这对于我们项目的空间&时间优化会显得更加智能。其他资源同理。

Xlua

xlua里有一个概念叫:cache表,不会对obj造成任何引用。
具体留到之后下次有机会介绍xlua obj引用的时候讲。

lua侧的对象管理

当我们既需要对某些对象进行管理又不希望对其回收造成一定困扰,就可以使用弱引用。以下两个场景就是典型的例子,当然,还有上述“用法”中的例子。

UIListener管理功能

之前踩坑内存泄漏的原因和button绑定event有很大关系,提到的其中一个解决方案就是建立一个weak table用来管理内部类的那些button,在其外部类被destroy的时候调用方法将这些button绑定的事件清空,可以避免内存泄漏问题。
代码:
------------------------------UIListener---------------------------

UIListener = {}
UIListener.eventGroup = {} --管理event的集合,在table.destroy中会按key释放
setmetatable(UIListener.eventGroup,{__mode = "k"})

--点击事件
function UIListener.AddButtonListener(btn,luaTable,luaFunction,luaTargetTable)
    btn.onClick:AddListener(function()
        if luaTable then
            luaFunction(luaTable)
        else
            luaFunction()
        end
    end)
    UIListener.AddEvent(btn.onClick,luaTargetTable)
end

--toggle事件
function UIListener.AddToggleListener(toggle,luaTable,luaFunction,luaTargetTable)
    toggle.onValueChanged:AddListener(function(value)
        if luaTable then
            luaFunction(luaTable,value)
        else
            luaFunction(value)
        end
    end)
    UIListener.AddEvent(toggle.onValueChanged,luaTargetTable)
end

--添加event事件到集合中
function UIListener.AddEvent(event,luaTargetTable)
    if luaTargetTable and event then
        if UIListener.eventGroup[luaTargetTable] == nil then
          --@RefType luaIde#CS.UnityEngine.UI.Button<>
          UIListener.eventGroup[luaTargetTable] = {}
        end
        table.insert(UIListener.eventGroup[luaTargetTable],event)
    end
end

--释放某table下所有event listener
function UIListener.RemoveEventListeners(luaTable)
    if UIListener.eventGroup and UIListener.eventGroup[luaTable] then
        for k, v in pairs(UIListener.eventGroup[luaTable]) do
            if v then
                v:RemoveAllListeners()
            end
        end
        UIListener.eventGroup[luaTable] = nil
    end
end

return UIListener

------------------------------table---------------------------

function table.destroy(tbTarget)
    if not tbTarget then
        return
    end

    --释放这个table下所有event listener
    UIListener.RemoveEventListeners(tbTarget)

    local stype = nil
    for key, value in pairs(tbTarget) do
        if value then
            stype = type(value)
            if stype and (stype == "userdata" or stype == "table") then
                tbTarget[key] = nil
            end
        end
    end
end

测试了一下还是很好用的:
前:
[图片]
后:
[图片]

你可能感兴趣的:(lua,unity,游戏,游戏引擎)