matlabdestructorhandlematlab-class

Why isn't this object destroyed when it is cleared from the workspace?


I am working on a MATLAB class which stores an interface object created with tcpip and includes a callback function for the interface object to use, as per the following example:

classdef wsg50_mini2 < handle

    properties
        TCPIP
    end

    %PUBLIC METHODS
    methods

        %CONSTRUCTOR
        function obj = wsg50_mini2(varargin)
            fprintf('################# I am created #################\n')

            obj.TCPIP = tcpip('localhost',1000);
            obj.TCPIP.OutputBufferSize = 3000;
            obj.TCPIP.InputBufferSize = 3000;
            obj.TCPIP.ByteOrder = 'littleEndian';
            obj.TCPIP.Timeout = 1;

            %Setting up Callbackfunction
            obj.TCPIP.BytesAvailableFcnMode = 'byte';
            obj.TCPIP.BytesAvailableFcnCount = 1;
            obj.TCPIP.BytesAvailableFcn = {@obj.TCP_Callback, obj};
        end
    end

    %PRIVATE METHODS
    methods (Access = private)

        %DESTRUCTOR
        function delete(obj)
            fprintf('################# Hey I am called! #################\n')
            instrreset
        end
    end

    %STATIC METHODS
    methods (Static)
        %TCP Callback
        %This function will be called if one Byte is available at the TCPIP
        %buffer.
        function TCP_Callback(tcpsocket,event,obj)
            fprintf('Loading 1 Byte Data From Buffer.\n')
        end
    end
end

When I clear my class the variable will be cleaned from the workspace, but the delete destructor function is not called. Why not?

I realized, that my Instruments are still active in the Instrument Control app. If I delete my Instruments from there, my delete destructor is be called.

I think it is some strange behaviour of the tcpip-class.


Solution

  • The problem is the line obj.TCPIP.BytesAvailableFcn = {@obj.TCP_Callback, obj};. The reference @obj.TCP_Callback creates a instance of the class inside the tcpip object, which is a property of the class itself.

    the most easiest solution is to clear obj.TCPIP.BytesAvailableFcn = '' inside the destructor and call the destructor manualy. This is okayish if the code is only used by its creator.

    To keep the object-oriented code intact, a Wrapper-Class works for the callback-function. The main file is updated with the Wrapper Class and a Listener as shown below. The Setup of the Callback is replaced with a combination of both of them. The Destructor needs then to call the destructors of the Wrapper (Which removes the instance of itself from the Callback) and of the Listener:

    classdef wsg50_mini2 < handle
    
        properties
            TCPIP
            TCPIPWrapper
            LH
        end
    
        %PUBLIC METHODS
        methods
    
            %CONSTRUCTOR
            function obj = wsg50_mini2(varargin)
                fprintf('################# I am created #################\n')
    
                % Create TCPIP Object
                obj.TCPIP = tcpip('localhost',1000);
    
                % Create TCPIP Wrapper
                obj.TCPIPWrapper = TCPIPWrapper(obj.TCPIP)
    
                % Add Listener
                obj.LH = listener(obj.TCPIPWrapper,'BytesAvailableFcn',@obj.onBytes);
    
                obj.TCPIP.OutputBufferSize = 3000;
                obj.TCPIP.InputBufferSize = 3000;
                obj.TCPIP.ByteOrder = 'littleEndian';
                obj.TCPIP.Timeout = 1;
    
            end
        end
    
        %PRIVATE METHODS
        methods (Access = private)
    
            %DESTRUCTOR
            function delete(obj)
                fprintf('################# Hey I am called! #################\n')
                % Destroy Listener
                delete(obj.LH);
                % destroy Wrapper
                delete(obj.TCPIPWrapper);
    
                instrreset
            end
        end
    
        %STATIC METHODS
        methods (Private)
            function onBytes(obj,~,~)
                fprintf('Loading 1 Byte Data From Buffer.\n')
            end
        end
    end
    

    Additional the Wrapper looks as following:

    classdef TCPIPWrapper < handle
        properties(Access=private)
            tcpip
        end
        events
            BytesAvailableFcn
        end
        methods
            % tcpipCallbackWrapper Constructor
            function obj = tcpipCallbackWrapper(tcpip)
                disp 'CONSTRUCTED tcpipCallbackWrapper'
                % Keep a reference to the tcpip object
                obj.tcpip = tcpip;
                % Adding this listener will result in the destructor not being
                % called automatically anymore; but luckily we have myClass
                % which will explicitly call it for us
                obj.tcpip.BytesAvailableFcnMode = 'byte';
                obj.tcpip.BytesAvailableFcnCount = 1;
                obj.tcpip.BytesAvailableFcn = @obj.bytesAvailableFcn;
            end
            % tcpipCallbackWrapper Destructor
            function delete(obj)
                disp 'DESTRUCTED tcpipCallbackWrapper'
                % Overwrite the callback with a new empty one, removing the
                % reference from the callback to our class instance, allowing
                % the instance to truly be destroyed
                obj.tcpip.BytesAvailableFcn = '';
            end
        end
        methods (Access=private)
            % Callback for BytesAvailableFcn
            function bytesAvailableFcn(obj,~,~)
                notify(obj, 'BytesAvailableFcn');
            end
        end
    end