查找句柄在作用域中的存储位置



MATLAB 句柄类对象在超出范围时将被删除。我有一些可以在应用程序的不同部分重用的对象,但是当它们不再在任何地方使用时,我想销毁它们。MATLAB 的内置生命周期行为允许我执行此操作,而无需维护任何其他全局列表来跟踪可能使用该对象的内容。

但是,我遇到一种情况,即我认为应该超出范围的对象仍在触发事件侦听器回调,这些回调作为对象析构函数的一部分被删除。我知道我认为这个对象的最后一个句柄应该存储在哪里,当我检查那里时,该句柄已被清除。因此,在其他地方的范围内必须有此句柄的实例。

我的应用程序是一个存储为其他对象的属性的复杂对象网络。我可以做些什么来帮助跟踪此对象的句柄在范围内存储的位置?

首先设置一个句柄类,其中包含要侦听的事件:

classdef Yard < handle
    events
        RollCall
    end
end
然后

是一个句柄类,该类通过显示一些文本然后通知自己的事件来对来自Yard对象的RollCall事件做出反应:

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 yardn', obj.id);
            notify(obj,'RollCall');
        end
    end
end

最后是一个类,它通过显示一些文本来对来自Kennel对象的RollCall事件做出反应:

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 %dn', obj.name, kennel.id);
        end
    end 
end

现在,将这些类的一些实例添加到工作区:

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);

我现在在作用域中有两个Kennel对象,其中的句柄在数组dogsDog对象的属性中引用。调用notify(Y,'RollCall')将生成以下输出:

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

如果删除了原来的两个Dog,则狗窝 2 将超出范围,但狗窝 1 仍处于活动状态,因为它仍被其余Dog引用:

>> dogs = dogs(3);
>> notify(Y,'RollCall')
Kennel 1 is in the yard
Rover is in kennel 1

但是,如果我在删除剩余Dog之前将狗窝 1 的额外句柄隐藏在范围内的其他位置,那么它将保持活动状态:

>> t = timer('UserData',dogs(1).kennel);
>> clear dogs
>> notify(Y,'RollCall')
Kennel 1 is in the yard

问题是,如果我不知道此额外引用是在何时何地创建的,以及为什么没有将其删除,我该怎么做才能调试对象的存在?

这是我处理

了很多的事情。您会看到,如果任何其他作用域中仍有对该对象的引用,则清除一个作用域中的句柄对象变量将不会触发析构函数。如果显式调用了 DogKennel 对象的析构函数,则计时器对象将具有对无效对象的引用。

Dog 的构造函数中创建了一个事件侦听器,其析构函数永远不会被调用,它保留对 Dog 实例的引用。

我会为DogKennel实现一个删除函数,它调用侦听器的删除函数。这是我经常使用的编码模式。

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

在您给出的示例中,您创建了一个 timer 对象,该对象还维护对 dogs 对象的引用。清除dogs不会消除此引用。清除timer对象也不会。必须显式删除timer对象。

我经常使用handle对象,通常在包含 GUI 的图形句柄对象(可以是图形、uicontainer、uipanel 等)上设计 GUI s 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句柄objects called in your code. I do this by setting the"DeleteFcn"回调。我包含此示例是因为它说明了需要如何仔细维护对处理对象的引用。

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

结束

我注意到handle类对象的另一个问题是,如果您在存在对这些对象的实时引用时修改并保存它们的classdef文件,您偶尔会遇到这样一种情况:存在对该对象的引用,您只能通过重置 Matlab 来清除这些对象。

最新更新