пятница, 19 апреля 2013 г.

Коварный Closure ActionScript-а / RU / Updated

Closure (замыкание) достаточно удобный механизм краткой записи, активно используется в функциональных языках и во многих динамических языках, вроде javascript. Его основной плюс, - это сокращенная запись рутинных действий и асинхронных вызовов. К примеру достаточно указать анонимную функцию для обработки массива, а не мусорить класс примитивными методами, либо анонимными классами. Так же не нужно создавать экземпляр класса с состоянием, по типу шаблона Command (только асинхронная), и слушать Event.COMPLETE или любое другое событие оповещающее о завершении операции. и т.д. Во многих случаях closure поможет вам записать код короче, а значит он будет более понятным, читабельным и более поддерживаемым. Конечно есть обратная сторона медали, вроде кучи вложений функций, но они хотя бы не заставляют бежать в другой класс, а из него в третий, чтобы выяснить поток исполнения.


К примеру, получить массив с теми же элементами, только возведенными в квадрат:

var squared:Array = array.map(function(value:Number):void { 
  return value * value; 
});

Javascript достаточно проницательно подтягивает в scope closure только используемые ссылки на объекты. Однако ActionScript не столь элегантен, поэтому, если вы решили использовать во flash механизм closure, то в него затянуться все ссылки из окружения, и что еще хуже, эти ссылки повиснут в памяти надолго, возможно и навсегда.

Можно принудительно обнулять локальные переменные, как в примере на строчке 118. Вот пример с обнулением:



Структура

1) В коде у нас есть основной класс - ClosureLeaks и вспомогательный - ClosureLeaksTest с тестовым методом execute в котором спрятан closure;
2) Так же есть массив _pool со слабыми ссылками, используется для отслеживания, какие экземпляры еще живы;
3) В ClosureLeaksTest есть статический массив _pool так же со слабыми ссылками, назначение аналогичное;
4) В ClosureLeaks так же есть несколько методов log* логирования объектов из _pool;


Эксперимент

1) Создаем 4 экземпляра ClosureLeaksTest, у троих запускаем еще метод execute(), содержащий closure.
2) В closure через некоторый промежуток времени обнуляем один из объектов;
3) Логируем состояние до и после;


Результат

Удаляется экземпляр ClosureLeaksTest, в котором мы не вызывали execute(), а так же локальные переменные, которые мы принудительно обнулили.

Но все оказалось еще хуже - в ходе экспериментов выяснилось, что если класс имеет (!) не статический метод с closure, а мы создали экземпляр этого класса, и вызвали метод с closure (в примере это execute) - экземпляр повисает навсегда.

Таким образом, если не хотите засорять память, используйте closure только в статических методах, и обнулите локальные переменные. А лучше не используйте closure вообще т.к. лучшая практика - это придерживаться одной практики.

Спасибо Makc3D, он также провел эксперимент, и получил аналогичный результат - scope не освобождается.

flash on 2013-4-20 - wonderfl build flash online

Update

Оказывается все не так уж плохо, ссылки в closure все таки чистятся, достаточно запустить System.gc(). Либо по убедить FlashPlayer, что памяти недостаточно, но как известно эта фаза поиска неиспользуемых участков памяти более ресурсоемкая, поэтому все же стоит избегать closure в часто запускаемых участках кода, т.к. память выделенная в ходе их исполнения будет еще долго болтаться балластом.

Пример closure с System.gc()

Closure Memory Leak with System.gc() - wonderfl build flash online

PS

Фото @bureanka_ua

PPS

Приятный, неожиданный побочный эффект статьи - пример кода стал 1№ в тот же день.

Комментариев нет:

Отправить комментарий

Press Any Key...