Архив Декабрь 2010

Как передать дополнительные параметры в слушатель (addEventListener передать переменную)

Многим приходилось сталкиваться с необходимостью передать дополнительные параметры в слушатель (listener). Мне это понадобилось для реализации ленивой загрузки(lazy loading) в дереве.

Как это работает:

  • пользователь нажимает на крестик
  • отправляется запрос на сервер (подгрузка нодов)
  • отображение загруженных нодов

Таким образом, возникает необходимость хранить где-то дополнительную информацию — куда вставить загруженные ноды? Есть конечно возможность выкрутиться: заполнить на сервере в нодах, например, parentID, затем искать по дереву нужный нод с id=parentID и в него уже ложить загруженные ноды. С точки зрения производительности не лучший вариант. А можно написать вспомогательный класс:

package
{
    import mx.collections.ArrayCollection;
    import mx.controls.Tree;
    import mx.rpc.events.ResultEvent;

    public class NodeLoader
    {
        private var tree:Tree;
        private var parentNode:Object;

        /**
         * Collection of NodeLoaders
         */
        private var listeners:ArrayCollection;

        public function NodeLoader(tree:Tree, parentNode:Object, listeners:ArrayCollection)
        {
            this.listeners = listeners;
            this.parentNode = parentNode;
            this.tree = tree;
        }

        public function onSuccess(event:ResultEvent):void
        {
            var nodes:ArrayCollection = ArrayCollection(event.result);
            //todo add nodes ... whatever

            clean();
        }

        public function clean():void
        {
            tree = null;
            parentNode = null;
            listeners.removeItemAt(listeners.getItemIndex(this));
            //todo clean any other references
        }
    }
}

Для использования этого класса необходимо лишь одно дополнительное поле — loaders:ArrayCollection, это нужно для того, чтобы использовать слабые ссылки при добавлении слушателей. Добавив NodeLoader в коллекцию мы предотвратим возможную сборку мусора. Разумеется, можно было бы обойтись без дополнительного поля, используя сильные ссылки и передавая в NodeLoader RemoteObject, чтобы была возможность отписаться от события, все зависит от вас.

Пример:

var loader:NodeLoader = new NodeLoader(tree, parentNode, loaders);
loaders.addItem(loader);
remote["load"].addEventListener(ResultEvent.RESULT, loader.onSuccess, false, 0, true);

Вдохновение от flasher.ru

Еще проще:


    public class Context
    {
        private var f:Function;
        private var context:*;
        private var args:Array;

        public function Context( context:*, f:Function, ...args)
        {
            this.f = f;
            this.context = context;
            this.args = args;
        }

        public function onSomething(...rest):void
        {
            f.apply(context, rest.concat(args));
            f = null;
            context = null;
            args = null;
        }
    }

    facade.getCategories(new Context(this, onCategoriesLoaded, new FilterValueVO()).onSomething, onFault);

    private function onCategoriesLoaded(event:ResultEvent, val:FilterValueVO):void
    {
    }
Реклама

, , ,

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

Базовая очистка базовых компонентов ч.1

Мусор

Фреймворк, по крайней мере Flex 3 SDK, написан так, что при всем правильном использовании и следовании flex-way, память будет расти как снежный ком. Проблема в том, что сборщик мусора слишком ленив (либо не все в порядке в датском королевстве), поэтому он откладывает сложные конструкции (например с циклическими ссылками) на потом. Я написал несколько классов, которые позволяют «очищать» компоненты которые больше не используются, оптимально подготавливая их к сборке мусора.

Для удобства заведем интерфейс IComponentCleaner — для каждого типа компонентов заимплементим свою реализацию.

package 
{
    public interface IComponentCleaner
    {
        /**
         * Cleans component: removes hidden references
         * @param object
         */
        function clean(object:Object, stage:CleanStage):void

        /**
         * Returns true if component can be cleaned.
         * @return true if component can be cleaned.
         */
        function probe(object:Object, stage:CleanStage):Boolean;
    }
}

Класс CleanStage я опишу позже, он используется для указания, какие ссылки можно очистить на данный момент, это связано с иерархией дисплейлиста и неотключаемым методом callLater (основной источник исключений, которые к тому же нельзя перехватить). В моем случае всего 3 варианта:
CLEAN_INIT — перед разрушением иерархии объектов, CLEAN_INDEPTH_INIT — после того как был построен список вложенных дисплей-объектов и CLEAN_COMPLETE — окончательная очистка.

package 
{
    public class CleanStage
    {
        public static const CLEAN_INIT:CleanStage = new CleanStage(0);
        public static const CLEAN_INDEPTH_INIT:CleanStage = new CleanStage(2);
        public static const CLEAN_COMPLETE:CleanStage = new CleanStage(3);

        public var stage:int;

        public function CleanStage(stage:int)
        {
            this.stage = stage;
        }
    }
}

Далее код класса ComponentCleaner, который объединяет внутри себя разные имплементации IComponentCleaner.

package
{

    import flash.utils.Dictionary;
    import flash.utils.getQualifiedClassName;

    import mx.utils.UIDUtil;

    public class ComponentCleaner
    {
        private static var _instance:ComponentCleaner;

        private var cleaners:Dictionary = new Dictionary(false);

        public function ComponentCleaner(seal:Seal)
        {
            if (!seal)
            {
                throw new Error("private");
            }
        }

        public static function get instance():ComponentCleaner
        {
            if (!_instance)
            {
                _instance = new ComponentCleaner(new Seal);

                //this order must not be changed! most problems come with callLater
                _instance.registerCleaner(new BoxCleaner());
                _instance.registerCleaner(new ScrollControllCleaner2());
                _instance.registerCleaner(new TreeCleaner());
                _instance.registerCleaner(new DisposableCleaner());
                _instance.registerCleaner(new ContainerCleaner());
                _instance.registerCleaner(new ViewStackCleaner());
                _instance.registerCleaner(new CanvasCleaner());
                _instance.registerCleaner(new ButtonCleaner());
                _instance.registerCleaner(new UIComponentCleaner());
                _instance.registerCleaner(new StyleCleaner());
                _instance.registerCleaner(new BindingsCleaner());
                _instance.registerCleaner(new LoaderCleaner());
                _instance.registerCleaner(new BitmapDataCleaner());
                _instance.registerCleaner(new SWFLoaderCleaner());
            }
            return _instance;
        }

        public function registerCleaner(cleaner:IComponentCleaner):void
        {
            if (!cleaner)
            {
                throw new Error("cleaner is null");
            }

            var cleanerUID:String = UIDUtil.createUID();
            cleaners[cleanerUID] = cleaner;
        }

        public function probe(object:Object, stage:CleanStage):Array
        {
            var availableCleaners:Array = [];
            for each (var componentCleaner:IComponentCleaner in cleaners)
            {
                if (componentCleaner.probe(object, stage))
                {
                    availableCleaners.push(componentCleaner);
                }
            }
            return availableCleaners;
        }

        public function clean(object:Object, stage:CleanStage):void
        {
            var availableCleaners:Array = probe(object, stage);
            for each (var componentCleaner:IComponentCleaner in availableCleaners)
            {
                componentCleaner.clean(object, stage);
            }
        }
    }
}

class Seal
{

}

Как видно, будут использованы 14 разных имплементаций.

Для устранения большинства проблем хватит нескольких строк кода. Первый в списке — BoxCleaner.

package
{
    import mx.containers.Box;
    import mx.core.mx_internal;

    public class BoxCleaner implements IComponentCleaner
    {
        public function clean(object:Object, stage:CleanStage):void
        {
            var box:Box = Box(object);
            if(box.mx_internal::layoutObject)
                box.mx_internal::layoutObject.target = null;
            box.mx_internal::contentPane = null;
            box.mx_internal::layoutObject = new BoxLayoutWorkAround();
        }

        public function probe(object:Object, stage:CleanStage):Boolean
        {
            return object is Box && stage == CleanStage.CLEAN_INIT;
        }
    }
}

Вспомогательный класс, очистки ссылок которые хранит BoxLayout. BoxLayoutWorkAround необходим для предотвращения исключений во время выполнения callLater (просто установить null в layoutObject недостаточно). Используется IDisposable интерфейс, описанный в предыдущих постах.

package 
{
    import mx.containers.utilityClasses.BoxLayout;
    import mx.core.Container;

    public class BoxLayoutWorkAround extends BoxLayout implements IDisposable
    {
        private var disposed:Boolean;

        public function BoxLayoutWorkAround()
        {
            target = new Container();
        }

        public override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
        {
            //do nothing
        }

        public override function measure():void
        {
            //do nothing
        }

        public function dispose():void
        {
            this.target = null;
            disposed = true;
        }

        public function isDisposed():Boolean
        {
            return disposed;
        }
    }
}

Большинство ссылок лежит в mx_internal неймспейсе, но в случае с биндингом вообще в приватных полях.

Продолжение следует…

, ,

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