MATLAB handle class objects are deleted when they go out of scope. I have objects that can be reused in different parts of an application, but which I want to destroy when they are no longer used anywhere. MATLAB's built in lifecycle behaviour allows me to do this without maintaining any additional global list to keep track of what might be using that object.
However I have a situation where an object I think should have gone out of scope is still firing event listener callbacks that are deleted as part of the object's destructor. I know where I think the last handle to this object in existence should have been stored, and sure enough when I check there that handle has been cleared. So there must be instances of this handle in scope somewhere else.
My application is a complex network of objects stored as properties of other objects. Is there anything I can do to help track down where in scope the handle to this object is being stored?
First set up a handle class with an event to listen to:
classdef Yard < handle
events
RollCall
end
end
Then a handle class that reacts to RollCall
events from a Yard
object by displaying some text and then notifying its own event:
classdef Kennel < handle
properties
id
yardListener
end
events
RollCall
end
methods
function obj = Kennel(yard,id)
obj.yardListener = event.listener(yard,'RollCall',@obj.Report);
obj.id = id;
end
function Report(obj,~,~)
fprintf('Kennel %d is in the yard\n', obj.id);
notify(obj,'RollCall');
end
end
end
And finally a class that reacts to RollCall
events from a Kennel
object by displaying some text:
classdef Dog
properties
name
kennel
kennelListener
end
methods
function obj = Dog(name,kennel)
obj.name = name;
obj.kennel = kennel;
obj.kennelListener = event.listener(kennel,'RollCall',@obj.Report);
end
function Report(obj,kennel,~)
fprintf('%s is in kennel %d\n', obj.name, kennel.id);
end
end
end
Now add some instances of these classes to the workspace:
Y = Yard;
% Construct two Dog objects, in each case constructing a new Kennel object to pass to the constructor
dogs = [Dog('Fido',Kennel(Y,1)) Dog('Rex',Kennel(Y,2))];
% Construct a third Dog, reusing the Kennel object assigned to dog(1)
dogs(3) = Dog('Rover',dogs(1).kennel);
I now have two Kennel
objects in scope, with handles referenced in the properties of the Dog
objects in the array dogs
. Calling notify(Y,'RollCall')
produces the following output:
Kennel 2 is in the yard
Rex is in kennel 2
Kennel 1 is in the yard
Rover is in kennel 1
Fido is in kennel 1
If the original two Dog
s are deleted then kennel 2 goes out of scope but kennel 1 remains active since it is still referenced by the remaining Dog
:
>> dogs = dogs(3);
>> notify(Y,'RollCall')
Kennel 1 is in the yard
Rover is in kennel 1
However if I hide an additional handle to kennel 1 somewhere else in scope before deleting the remaining Dog
then it will remain active:
>> t = timer('UserData',dogs(1).kennel);
>> clear dogs
>> notify(Y,'RollCall')
Kennel 1 is in the yard
The question is if I don't know where or when this extra reference was created and why it hasn't been deleted, what can I do to debug the existence of the object?
This is something I have dealt with a lot. You see, clearing the handle object variable in one scope will not trigger the destructor if there is still a reference to the object in any other scope. If you explicitly called the destructor for the Dog
and Kennel
objects, your timer object would have a reference to an invalid object.
In the constructor for Dog
there is an event listener created, whose destructor is never called which retains a reference to an instance of Dog
.
I would implement a delete function for Dog
and Kennel
which called
the delete function for the listeners. Here is a coding pattern I use a lot.
classdef MyClass < handle
properties
listener % listeners are themselves handle objects
end
methods
%% Class constructor creates a listener
%% Class methods
function delete(obj)
% do this for all handle object properties
% Also, avoid calling constructors for handle objects in
% the properties block. That can create references to handle
% objects which can only be cleared by a restart
if ishandle(obj.listener)
delete(obj.listener)
end
end
end
end
In the example you gave, you created a timer
object which also maintains a reference to your dogs
object. Clearing dogs
does not eliminate this reference. Neither would clearing the timer
object. You must explicitly delete timer
objects.
I use handle
objects a lot, and usually design GUIs to go with them (aside, never, never, never use GUIDE for this. It is the worst thing ever). You have to be sure to clear all references to any
handleobjects called in your code. I do this by setting the
'DeleteFcn'` callback on the graphics handle object that contains the GUI (could be a figure, uicontainer, uipanel, etc.). I include this example because it illustrates how carefully references to handle objects need to be maintained.
function dispGUI(handleOBJ,hg)
%% build gui in hg
set(hg,'DeleteFcn',@deleteCallBack)
h = uicontrol(hg,'Callback',@myCallback)
function myCallback(h,varargin)
% So now there is a reference to handleOBJ
% It will persist forever, even if cleared in the caller's
% workspace, thus we clear it when the containing graphics
% object delete callback is triggered.
if isvalid(handleOBJ)
set(h,'string','valid')
else
set(h,'string','invalid')
end
end
function deleteCallBack(varargin)
% calling clear on handleOBJ clears it from the scope of the
% GUI function
% also call clear on the containing function
clear handleOBJ
clear(mfilename)
end
end
Another issue I have noticed with handle
class objects, is that if you modify and save their classdef
file while there are live references to these objects you can occasionally get into a situation where there are references to the object that you can only clear by resetting Matlab.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With