You are correct, when you remove all references to a table, it is deleted. This is taken care of by Lua's garbage collection:
However, there are reasons you might want some sort of cleanup function:
- If your object subscribes to any events that will still exist when you want your object to be deleted, it needs to unsubscribe from them (or else the object can't be cleaned up because the event still holds a reference to it)
- If your object added itself to a collection of (in your example) apples (perhaps so some system can track how many apples are in the game at the moment and iterate through them as necessary), naturally you'll need to remove it from that collection (unless that collection uses weak keys/values)
- If you know the object will stay in memory for a while longer (ex maybe apples are referenced by an AppleTree which you intend to stay in memory for a longer period of time) and each Apple contains a reference to a large table or resource (that is specific to that apple), you should remove the references to that larger table/resource to allow it to be garbage collected.
For all other cases, you can expect that it will be removed from memory as needed. (In regular Lua you can force garbage collection to occur immediately with the debug library's collectgarbage, but Roblox has removed this functionality.)
For example, say these items are part of a player's inventory, and you have a dictionary tracking each player's inventory like playerInventory[player] = inventoryList. So long as you perform playerInventory[player] = nil when the player leaves the server, you don't need to do anything special to each inventory item, so long as the items don't do any of the above points.
If you wish to ensure that a piece of code isn't going to leak memory, you can use tables with weak keys/values to test, such as these functions I've written for that purpose:
local function waitForGarbageCollection()
local t = setmetatable({}, {__mode="v"})
t[1] = {}
while t[1] do wait() end
end
local function LeaksMemory(create, cleanup)
local t = setmetatable({}, {__mode = "v"})
local numValues
do
local newValues = {create()}
numValues = #newValues
if numValues == 0 then error("create returned 0 values") end
for i = 1, #newValues do
t[i] = newValues[i]
end
if cleanup then cleanup(unpack(newValues)) end
end
waitForGarbageCollection()
local numLeaked = 0
local report = ": "
for i = 1, numValues do
if t[i] ~= nil then
numLeaked = numLeaked + 1
if numLeaked == 1 then
report = report .. i
else
report = report .. ", " .. i
end
end
end
print(numValues > 1
and string.format("%d/%d leaked%s", numLeaked, numValues, numLeaked > 0 and report or "")
or numLeaked == 1 and "leaked" or "no leak")
end
local Item = {}
Item.__index = Item
function Item.new(ID, Amount)
local NewItem = {}
setmetatable(NewItem, Item)
NewItem.ID = ID or 1
NewItem.Amount = Amount or 0
NewItem:Connect()
return NewItem
end
function Item:Inc()
self.Amount = self.Amount + 1
end
function Item:Connect()
self.con = game:GetService("RunService").Heartbeat:Connect(function()
self:Inc()
end)
end
function Item:Destroy()
if self.con then
self.con:Disconnect()
end
end
LeaksMemory(Item.new)
LeaksMemory(Item.new, Item.Destroy)
local a = 0
function Item:Connect()
self.con = game:GetService("RunService").Heartbeat:Connect(function()
a = a + 1
end)
end
LeaksMemory(Item.new)
function Item:Connect()
local function func() a = a + 1 end
self.con = game:GetService("RunService").Heartbeat:Connect(func)
return func
end
function Item.new(ID, Amount)
local NewItem = {}
setmetatable(NewItem, Item)
NewItem.ID = ID or 1
NewItem.Amount = Amount or 0
return NewItem, NewItem:Connect()
end
LeaksMemory(Item.new)
LeaksMemory(Item.new, Item.Destroy)
3 comments
Thanks for the explanation! I'm a little confused on the whole Item:Connect() part, do I need it or is it just an example? Also, shouldn't setting all the things inside an item to nil make it get garbage collected?
Right, the whole "Item:Connect" part is illustrating an example of a memory leak. If you have an object that doesn't do any of the things I mentioned in the bullet points, you don't need any cleanup code, it'll happen automatically - setting an item's fields to "nil" just wastes time in that case.
If it connected to an event (as in Item:Connect), setting item.con to nil won't help -- that simply removes item's reference to 'con', but the event also has a reference to con that you can't get rid of unless you Destroy the event (not possible for events like Heartbeat) or call :Disconnect on the connection.