Забавная ошибка при использовании анонимных методов
Во время работы над проектом вчера наткнулся на достаточно увлекательную ошибку при работе с анонимными методами, которая может быть не всегда очевидна. Отчасти это ошибка моя как разработчика, с другой стороны есть и некоторая недоработка со стороны разработчиков Delphi.
На практике в приложении была реализована такая ситуация: есть набор экранных форм, и менеджер этих форм. Менеджер имеет метод для отображения формы, назовем его TScreenManager.ShowScreen, берет форму и отображает его в некотором внешнем контейнере. Контейнер имеет кнопку для возврата к предыдущему окну. Порядок форм в целом известен, и форма знает куда ей нужно возвращаться, поэтому экспортирует процедуру возврата. Процедура естественно использует все тот же ShowScreen для отображения уже предыдущего окна.
Не все окна могут иметь процедуру возврата, и там где ее нет саму кнопку "назад", показывать тоже не требуется. В итоге получается, что нам одновременно надо узнать что метод доступен, и иметь возможноть его вызывать. Для таких целей нужен указатель на процедуру. То есть вернув указатель мы сразу можем проверить что он не нулевой, и сохранив его в дальнейшем сможем вызывать сам метод. Поскольку мы работаем с классами/обектами, то в данном случае у нас не просто указатель на процедуру, а указатель на метод объекта, то есть в терминологии Delphi - событие (procedure() of object).
Альтернативный вариант - использовать анонимный метод. То есть когда менеджер показывает окно, он вызывает метод получения процедуры возврата, наподобие FBackProc := window.getBackProc. Таким образом ссылка сохраняется, а если вернулся nil, то кнопка Назад скрывается.
Теперь к сути. Для перехода назад, как я уже упомянул, используется тот же метод ShowScreen, который получает ссылку обратной процедуры уже у нового окна, тем самым, замещая в итоге значение текущей ссылки FBackProc. Помня о том, что анонимный метод реализуется в виде интерфейса, то обнуление ссылки приводит к освбождению этого интерфейса. Таким магическим образом во время работы анонимного метода объект его реализующий разрушается до его (метода) завершения.
Для иллюстрации скриншот отладчика во время выполнения кода:
Как видим в начале метода число ссылок на интерфейс равно 1. Далее при выполнении метода TTestMethod.SaveTestMethod() мы открываем новое окно, получаем новую ссылку "Назад" и старая обнуляется. В итоге к концу метода мы получаем уже следующую картину:
при этом число ссылок уже равно нулю, интерфейс "отпустили".
В этом демо-примере (в хе2) сейчас у меня все работает. В рабочем проекте (ХЕ4) ссылка self->self (интерфейс -> ссылка на объект в котором был создан метод) обнулялась, и значение поля FBool не устанавливалось в true. А в более общем случае должно приводить к AV т.к. пишется неизвестно в чью память.
В итоге получается с одной стороны этой мой косяк, что ссылка на интерфейс была заменена во время работы самого метода. С другой стороны, при "внутреннем" вызове анонимного метода неплохо было бы увеличивать число ссылок, и уменьшать по завершении, чтобы интерфейс гарантированно не разрушился во время выполнения.
16.01.2014 в 20:47
16.01.2014 в 22:20
we should always remember, that anonimous method is implemented as Interface, so nil-ing the reference to method changes methods RefCount to 0 and releases it.
so, just do not change method reference inside method itself :)
16.01.2014 в 20:49
16.01.2014 в 22:43