我正试图弄清楚在Vala中类间信号的良好实践是什么。不知何故,似乎完全没有关于这个主题的文档。在学习了如何将信号微弱地连接到处理程序之后,问题仍然存在,当信号处理程序被破坏时,如何最好地断开连接。
让我们考虑一个简单的例子。我们有两个类,Foo
和Bar
,其中Bar
的实例发出信号bar_signal
,该信号由Foo
的实例处理。然而,该信号连接较弱,允许Foo
实例被删除,这就是为什么连接仍然有效的原因:
class Bar : Object {
public signal void bar_signal();
}
class Foo : Object {
string tag;
ulong hid;
Bar bar;
public Foo( Bar bar, string tag ) {
this.tag = tag;
this.bar = bar;
weak Foo weak_this = this;
hid = bar.bar_signal.connect( weak_this.handle );
}
~Foo() {
stdout.printf( "foo finalizedn" );
}
private void handle() {
stdout.printf( "handler: %sn", tag );
}
}
public static void main( string[] args ) {
Bar bar = new Bar();
{
Foo foo = new Foo( bar, "x" );
bar.bar_signal(); // writes "handler: x"
} // foo destroyed at end of block
bar.bar_signal(); // writes nothing
}
根据该注释,在Foo
实例被销毁后,处理程序引用在bar_signal
中仍然是一个悬空指针,因为我们还没有断开它的连接。
我已经考虑添加
bar.bar_signal.disconnect( handle );
Foo
的析构函数,工作良好。
然而,我认为在某些情况下,handle
可能无法从析构函数访问,例如,当处理程序是一个闭包时。
问题:我们还可以使用相应connect
调用的返回值断开处理程序与信号的连接。例如,在connect
调用之后立即执行此操作时,效果非常好。但为什么它会在析构函数中失败呢?
~Foo() {
stdout.printf( "foo finalizedn" );
bar.disconnect( hid ); // <--!!
}
出现以下提示时失败:
GLib GObject警告**:实例"0x8c8820"没有id为"1"的处理程序
更新
更新1:有趣的是,如果bar.disconnect( hid )
调用是在Foo
的dispose
方法中完成的,那么它是有效的。根据GObject
的内存管理文档,dispose
是在对象最终确定之前调用的(我认为它对应于Vala中的析构函数),其目的是剪切对其他对象的所有引用值得注意的是,正如@AlThomas所指出的,"当信号处理程序用户数据被破坏时,信号处理程序[…]不会自动断开连接。">那么,为什么我们可以断开信号处理程序与dispose
的连接,而不是在Foo
最终确定时,这正是GObject文档实际建议的?
我想到的唯一解释是:与我之前被告知的相反,Vala/GLib在处理和最终确定对象之间自行断开信号处理程序。然而,这听起来不太可能,所以我真的很感激进一步的澄清。
更新2:事实证明,上面并没有那么错误:)
一般来说,Vala的信号是使用GLib的GObject信号的观测器模式的实现。因此,GLib自己的文档是一个很好的信息来源。例如信号和如何创建和使用信号。后者说明:
GType中的信号系统非常复杂和灵活:它的用户可以在运行时将任何数量的回调(以存在绑定的任何语言实现)连接到任何信号,并在信号发射过程的任何状态下停止任何信号的发射。
一个复杂的主题需要良好的文档,您的Vala特定问题有助于建立该主题的知识体系。
对于您的问题,GLib文档中最相关的部分可能是信号处理程序的内存管理。本节建议:
当发出信号的对象完成时,信号处理程序会自动断开连接,但当信号处理程序用户数据被破坏时,它们不会自动断开。。。管理此类用户数据有两种策略。第一种是在用户数据(对象)完成时断开信号处理程序(使用
g_signal_handler_disconnect()
或g_signal_handlers_disconnect_by_func()
);这必须手动实现。对于非线程程序,g_signal_connect_object()
可以用来自动实现。。。第二是要有一个强有力的参考。。。建议采用第一种方法。。。
深入了解Vala的工作方式时,一个有用的选项是带有valac
的--ccode
开关。如果您查看程序中的C代码,您将看到foo_construct
函数包含对g_signal_connect_object()
的调用。因此,Vala使用文档中概述的第一种方法,但在建议的三个函数调用中,Vala通过调用g_signal_connect_object()
使用自动方法。
没有必要手动断开连接,这就是您收到警告的原因。GLib已断开信号。GLib文档在这里可能有点混乱,因为手动过程的一部分有一个可以自动断开连接的调用。
GLib关于对象销毁的文档建议"对象的销毁过程分为两个阶段:处置和最终确定"。因此,假设断开连接是在处置阶段完成的,但您必须查看GLib的源代码才能确认这一点。