Desconexión de controladores de señales

En el Listado, se llama a button_clicked_cb() cada vez que el objeto button emite la señal "clicked". Si el objeto button todavía está vivo después de que my_class se haya liberado, cuando la señal se emita nuevamente habrá un pequeño problema ... Entonces, en el destructor de MyClass, el manejador de señales (es decir, la devolución de llamada) debe desconectarse. ¿Como hacer eso?

Las funciones g_signal_connect*() en realidad devuelven un ID del manejador de señales, como un entero gulong siempre mayor que 0 (para conexiones exitosas). Al almacenar ese ID, es posible desconectar ese manejador de señal específico con la función g_signal_handler_disconnect().

A veces también queremos desconectar el controlador de señales simplemente porque ya no estamos interesados en el evento.

El Listado muestra un ejemplo completo de cómo desconectar un manejador de señales cuando se libera su argumento user_data. Volvemos a un ejemplo con un corrector ortográfico, porque el ejemplo con el botón GTK no se ajusta bien a la situación.

📌 Nota: La mayoría de las veces, un widget GTK no vive más tiempo que el contenedor al que se agrega, y el El objeto que escucha la señal del widget suele ser el propio contenedor. Entonces, si el widget muere al mismo tiempo que el contenedor, no es posible que el widget envíe una señal mientras su contenedor ya ha sido destruido. En ese caso, no tiene sentido desconectar la señal en el destructor del contenedor, ya que en ese punto el widget ya está liberado; y es más difícil para un objeto muerto enviar una señal.

📌 Nota: Cuando digo que es más difícil, en realidad es imposible, por supuesto.

El argumento de devolución de llamada user_data es una instancia de MyTextView, con MyTextView implementado con un estilo semi-OOP. Dado que el objeto del corrector ortográfico puede vivir más tiempo que la instancia de MyTextView, la señal debe desconectarse en el destructor MyTextView.

#include <glib-object.h>

typedef struct _MyTextView MyTextView;

struct _MyTextView
{
  GspellChecker *spell_checker;
  gulong word_added_to_personal_handler_id;
};

static void
word_added_to_personal_cb (GspellChecker *spell_checker,
                           const gchar   *word,
                           gpointer       user_data)
{
  MyTextView *text_view = user_data;

  g_message ("La palabra '%s' se ha agregado al diccionario personal "
             "del usuario. text_view=%p se actualizara en consecuencia.",
             word,
             text_view);
}

MyTextView *
my_text_view_new (GspellChecker *spell_checker)
{
  MyTextView *text_view;

  g_return_val_if_fail (GSPELL_IS_CHECKER (spell_checker), NULL);

  text_view = g_new0 (MyTextView, 1);

  /* Almacenamos el GObject de spell_checker en la variable
   * de instancia, por lo que aumentamos el recuento de referencias
   * para asegurarnos de que el corrector ortografico permanece activo
   * durante la vida util de la text_view.
   *
   * Tenga en cuenta que el corrector ortografico se proporciona de
   * forma externa, por lo que el corrector ortografico puede vivir mas
   * tiempo que text_view, de ahi la necesidad de desconectar la señal
   * en my_text_view_free().
   */
  text_view->spell_checker = g_object_ref (spell_checker);

  text_view->word_added_to_personal_handler_id =
    g_signal_connect (spell_checker,
                      "word-added-to-personal",
                      G_CALLBACK (word_added_to_personal_cb),
                      text_view);

  return text_view;
}

void
my_text_view_free (MyTextView *text_view)
{
  if (text_view == NULL)
    return;

  if (text_view->spell_checker != NULL &&
      text_view->word_added_to_personal_handler_id != 0)
    {
      g_signal_handler_disconnect (text_view->spell_checker,
                                   text_view->word_added_to_personal_handler_id);

      /* Aqui no es necesario restablecer el valor a 0 porque
       * text_view de todos modos se liberara, es solo para tener
       * un ejemplo mas completo.
       */
      text_view->word_added_to_personal_handler_id = 0;
    }

  /* El equivalente de:
   * if (text_view->spell_checker != NULL)
   * {
   *   g_object_unref (text_view->spell_checker);
   *   text_view->spell_checker = NULL;
   * }
   *
   * Despues de disminuir el recuento de referencias, spell_checker
   * aun puede estar activo si otra parte del programa todavia
   * hace referencia al mismo corrector ortografico.
   */
  g_clear_object (&text_view->spell_checker);

  g_free (text_view);
}

Listado: Desconectar un manejador de señales cuando se libera su argumento `user_data`.

En realidad, hay otras funciones de g_signal_handler*() que permiten desconectar los manejadores de señales:

  • g_signal_handlers_disconnect_by_data()
  • g_signal_handlers_disconnect_by_func()
  • g_signal_handlers_disconnect_matched()

Habría sido posible utilizar una de las funciones anteriores en el Listado, y habría evitado la necesidad de almacenar word_added_to_personal_handler_id. La función básica g_signal_handler_disconnect() se ha utilizado con fines de aprendizaje.

Tenga en cuenta también que si MyTextView fuera una clase GObject, habría sido posible conectarse a la señal del corrector ortográfico con g_signal_connect_object(), y habría eliminado por completo la necesidad de desconectar manualmente el controlador de señal en el destructor MyTextView. Una (pequeña) razón más para aprender a crear subclases de GObject.