compile4fun

Flex/Java developer, Minsk

Домашняя страница: https://compile4fun.wordpress.com

Как преобразовать цвет из hex-строки("0xFF0000") в uint?

Ответ простой:

var color:uint =  uint(«0xff0000»);

Реклама

, , , ,

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

Как отключить механизм callLater

Основной проблемой при разрушении объектов вручную становится метод callLater. Даже если объект удален из дисплейлиста элементы жизненного цикла компонентов (validate/invalidate/update) продолжают работать, а они в свою очередь активно пользуются механизмом callLater. Это не позволяет обнулять некоторые ссылки, которые используются в методах добавленных для выполнения с помощью callLater. Отключить его, как оказалось, легко — необходимо написать декоратор для SystemManager (имплементация ISystemManager) и перекрыть в нем метод addEventListener, отключив добавление слушателя для событий типа FlexEvent.RENDER и FlexEvent.ENTER_FRAME. Подменив мененджер на свой необходимо также вызвать component.mx_internal::cancelAllCallLaters(), чтобы удалить ранее добавленные методы. Вуаля!

,

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

Label: truncateToFit — проблемы с производительностью

Неожиданно открывшийся тикет поставил в тупик: дерево отображающее XMP начинало тормозить без видимой причины при раскрытии некоторых нодов. Как оказалось, это были ноды содержащие кодированые в base64 изображения(достаточно большой текст). Казалось бы что тут такого, xml небольшой, значит датадескриптор тормозить не может, видимого текста мало, компонентов в рендерере немного, почему все так плохо? Оказалось проблема скоро пойдет в школу. Если лень перейти по ссылке, быстрый ответ —  truncateToFit, развернутый:

while (s.length > 1 && textWidth + TEXT_WIDTH_PADDING > w) {
    s = s.slice(0, -1);
    super.text = s + truncationIndicator;
}

, ,

1 комментарий

Рисование стрелок во Flex и Flash

Шагая по гуглу нашел весьма интересный и полезный утилитный класс для рисования всевозможных стрелок GraphicsUtil. Прилагаю скриншот:

GraphicsUtil

GraphicsUtil

демо

, ,

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

Как передать дополнительные параметры в слушатель (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 неймспейсе, но в случае с биндингом вообще в приватных полях.

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

, ,

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

Stirling engine на соленоиде

Этот пост совсем не про флекс :)Stirling engine

Дви́гатель Сти́рлинга — тепловая машина, в которой жидкое или газообразное рабочее тело движется в замкнутом объёме, разновидность двигателя внешнего сгорания. Основан на периодическом нагреве и охлаждении рабочего тела с извлечением энергии из возникающего при этом изменения объёма рабочего тела. Может работать не только от сжигания топлива, но и от любого источника тепла. (wiki)

Просмотрев на ютьюбе десяток видео (например это) я наткнулся на вариант, где вместо тепла, цилиндра и воздуха используется обычный электромагнит — соленоид (или даже два). Воспользовавшись останками железного конструктора, магнитофона «Весна», китайского кассетного плеера и парой скрепок, я сделал свой работающий двигатель.

Несмотря на то, что это совсем не двигатель Стирлинга, вещица забавная, если будет время сделаю еще одну с магитами помощнее.

, ,

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

Имплементация WeakReference

Часто возникает необходимость хранить ссылку на объект, не препятствуя при этом сборке этого объекта сборщиком мусора. Для этого уже есть примеры, но тем не менее :). Рассмотрим сначала самый важный класс — WeakReferenceHolder (непосредственно имплементация WeakReference):

package
{
    import flash.utils.Dictionary;

    public class WeakReferenceHolder
    {
        private var profiledObject:ProfiledObject; //stores object reference info
        private var ref:Dictionary = new Dictionary(true);//weak keys enabled

        public function WeakReferenceHolder(reference:Object = null, description:String = null)
        {
            if (reference != null)
            {
                profiledObject = ProfileObjectFactory.instance.create(reference, description);
                ref[reference] = profiledObject;
                //later i will publish sources for custom memory profiler
                //see https://compile4fun.wordpress.com/2010/10/31/memory-profile/
                //GCWatcher.instance.watch(reference, description ? description : "");
            }
        }

        public function set(reference:Object, description:String = null):void
        {
            clear();
            if (reference != null)
            {
                profiledObject = ProfileObjectFactory.instance.create(reference, description ? description : "");
                ref[reference] = profiledObject;
                //later i will publish sources for custom memory profiler
                //see https://compile4fun.wordpress.com/2010/10/31/memory-profile/
                //GCWatcher.instance.watch(reference, description ? description : "");
            }
        }

        public function clear():void
        {
            if (this.reference)
            {
                for (var reference:Object in ref)
                {
                    delete ref[reference];
                }
                trace("Free reference " + profiledObject.toString());
                profiledObject = null;
            }
        }

        public function get():Object
        {
            if(reference)
            {
                return reference;
            }
            else if(profiledObject != null)
            {
                trace("GC collected: " + profiledObject.toString());
                profiledObject = null;
            }
            return null;
        }

        private function get reference():Object
        {
            var result:Object;
            for (var reference:Object in ref)
            {
                result = reference;
            }
            return result;
        }
    }
}

Идея вобщем-то довольна проста: использовать встроенную возможность flash.utils.Dictionary — weakKeys («If the only reference to an object is in the specified Dictionary object, the key is eligible for garbage collection and is removed from the table when the object is collected.»). Дополнительная возможность — логирование информации об объекте, а также интеграция(в будущем) с профайлером памяти). Для этого понадобятся два дополнительных класса: ProfiledObject — value-объект, и ProfileObjectFactory — конструктор объектов типа ProfiledObject.
ProfiledObject:

package
{
    public class ProfiledObject
    {
        public var name:String;
        public var size:Number;
        public var currentSize:Number;
        public var watchStack:Object;
        public var clazz:String;
        public var superClazz:String;
        public var date:String;

        public var description:Object;

        public function ProfiledObject(name:String, size:Number, allocationStack:Object, clazz:String, superClazz:String)
        {
            this.name = name;
            this.size = size;
            this.watchStack = allocationStack;
            this.clazz = clazz;
            this.superClazz = superClazz;
        }

        public function toString():String
        {
            return String(date) + "\t" + String(clazz) + "\t" + String(clazz) + "\t"  + String(description) + "\t" + String(size);
        }
    }
}

ProfileObjectFactory:

package
{
    import flash.sampler.getSize;
    import flash.utils.getQualifiedClassName;
    import flash.utils.getQualifiedSuperclassName;

    import mx.collections.ArrayCollection;
    import mx.formatters.DateFormatter;
    import mx.utils.UIDUtil;

    public class ProfileObjectFactory
    {
        private static var dateFormatter:DateFormatter = new DateFormatter();

        private static var _instance:ProfileObjectFactory;

        public function ProfileObjectFactory()
        {
        }

        public static function get instance():ProfileObjectFactory
        {
            if (!_instance)
            {
                dateFormatter.formatString = "JJ:NN:SS";
                _instance = new ProfileObjectFactory();
            }
            return _instance;
        }

        public function create(obj:Object, description:String = ""):ProfiledObject
        {
            var profiledObject:ProfiledObject = new ProfiledObject(null, 0, getStack(), null, null);
            if (obj != null)
            {
                profiledObject.name = UIDUtil.createUID();
                profiledObject.description = (description && description.length > 0 ? description : "") + " " + obj.toString();
                profiledObject.size = getSize(obj);
                profiledObject.clazz = getQualifiedClassName(obj);
                profiledObject.superClazz = getQualifiedSuperclassName(obj);
                var date:Date = new Date();
                profiledObject.date = dateFormatter.format(date) + "." + date.getMilliseconds();
                return profiledObject;
            }
            else
            {
                return null;
            }
        }

        private function getStack():Object
        {
            var result:ArrayCollection = new ArrayCollection();
            try
            {
                throw new Error("Allocation trace:");
            }
            catch(e:Error)
            {
                var stack:String = e.getStackTrace();
                if (stack != null && stack.length > 0)
                {
                    result = new ArrayCollection(escape(stack).split("\n"));
                }
            }

            var obj:Object = {};
            var k:int = 1;
            for (var i:int = 0; i  3)
                {
                    obj[addLeadingZero(k)] = result.getItemAt(i);
                    k++;
                }
            }
            return obj;
        }

        public function addLeadingZero(n:Number):String
        {
            var out:String = String(n);

            if (n > -1 && n < 10)
            {
                out = "0" + out;
            }

            return out;
        }

        private function escape(str:String):String
        {
            var result:String = "";
            var instancePattern1:RegExp = /(\\)/gmi;
            result = str.replace(instancePattern1, "/");
            return result;
        }
    }
}

Использовать можно примерно так:

var control:WeakReferenceHolder = new WeakReferenceHolder(object, object ? "ServiceFrontend: " + object.toString() : null);
trace(control.get());

Пока времени больше нет, но материала еще много, продолжим тему чуть позже.

, , ,

2 комментария

Профайлеры: Flex Builder 3 vs Fash Builder 4

Adobe Flash Builder 4До недавнего времени я пользовался только профайлером из Flex Builder 3. Для большого приложения ему нехватает производительности, приходиться тратить много времени, чтобы прокликать нужный функционал.

И вот скачал триальную 60-дневную версию Flash Builder 4. И что же вы думаете? Разочарование. В принципе функционал один и тот же, даже в чем-то хуже, чем у предшественника. Связано это с казалось бы благой целью — исключить из списка циклические ссылки, те что в третьей версии заставляли ходить по кругу. Проблема заключается в том, что компоненты имеющие циклические ссылки собираются гораздо хуже, и ничего, впрочем, не мешает эти ссылки обнулить, ускорив в разы сборку мусора.

По моим наблюдениям, сборщик мусора достаточно ленив, и сколько-нибудь сложные для сборки вещи он откладывает на потом (т.е. фактически никогда не соберет), в то время как простые объекты без ссылок охотно собираются.

Работает новый профайлер медленне существенно, и потребляет больше памяти (словил пару раз OutOfMemory). Больше никаких достоинств замечено не было.
Таким образом, имеет смысл пользоваться профайлером из Flex Builder 3, если нет других объективных причин этого не делать.

, , , ,

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

Правильно выбрасываем мусор: Tree и TreeItemRenderer

TreeОснованные на ListBase компоненты фреймворка имеют много проблем и по моему опыту с трудом освобождают память, возможно для этого нужно выполнение какого-то хитрого условия внутри сборщика мусора, но мне такого видеть не доводилось. Профайлер обычно показывал кучу ссылок на рендереры, промежуточные объекты и прочее (все это оставалось в памяти), и не было очевидно сможет ли компонент быть утилизирован или нет. Решено было очистить все, что можно, и вот что получилось.

Рассмотрим оптимизированные для сборщика мусора DisposableTree и, соответственно, DisposableItemRenderer (см. IDisposable). Я выкладываю исходники прямо в блог, чтобы можно было быстро ознакомиться.

DisposableTree:

package gc
{
    import flash.display.DisplayObject;
    import flash.display.Shape;
    import flash.display.Sprite;

    import mx.containers.Canvas;
    import mx.controls.Tree;
    import mx.controls.listClasses.ListBaseContentHolder;
    import mx.core.FlexSprite;
    import mx.core.UIComponent;
    import mx.core.mx_internal;
    import mx.skins.halo.HaloBorder;

    /**
     * Tree optimized for garbage collection.
     */
    public class DisposableTree extends Tree implements IDisposable
    {
        protected var disposed:Boolean;

        public function DisposableTree()
        {
            super();
            this.itemRenderer = new DisposableItemRenderer(); //use optimized renderer
        }

        public function dispose():void
        {
            if (disposed)
                return;
            if (listContent)
            {
                if (listContent.visibleData) //clear dynamic object
                {
                    for (var prop0:String in listContent.visibleData)
                    {
                        clearRenderer(listContent.visibleData[prop0]);
                        delete listContent.visibleData[prop0];
                    }
                }
                if (listContent.listItems) //clear array of itemRenderers
                {
                    while (listContent.listItems.length > 0)
                    {
                        clearRenderer(listContent.listItems.pop());
                    }
                }
                if (listContent.rowInfo) //clear array of ListRowInfo objects
                {
                    while (listContent.rowInfo.length > 0)
                    {
                        listContent.rowInfo.pop();
                    }
                }

                if (freeItemRenderers) //clear array of itemRenderers
                {
                    while (freeItemRenderers.length > 0)
                    {
                        var renderer0:Object = freeItemRenderers.pop();
                        clearRenderer(renderer0);
                    }
                }

                if (reservedItemRenderers) //clear array of itemRenderers
                {
                    for (var prop1:String in reservedItemRenderers)
                    {
                        clearRenderer(reservedItemRenderers[prop1]);
                        delete reservedItemRenderers[prop1];
                    }
                }

                if (unconstrainedRenderers) //clear array of itemRenderers
                {
                    for (var renderer1:Object in unconstrainedRenderers)
                    {
                        clearRenderer(renderer1);
                        delete unconstrainedRenderers[renderer1];
                    }
                }

                listContent.selectionLayer = new FlexSprite(); //clear selectionLayer property with empty FlexSprite
                listContent.mx_internal::_parent = null; //clear evil mx_internal::_parent references
                listContent.styleName = null; //styleName can be String or any Object so it must be reset
                listContent = new ListBaseContentHolder(null); //listContent must be also reset as DisplayObject reference
            }

            //clear dynamic object
            for (var prop2:String in selectionIndicators)
            {
                delete selectionIndicators[prop2];
            }

            //clear renderer factories from Dictionary with strong references
            for (var factory0:Object in factoryMap)
            {
                clearRenderer(factory0);
                delete factoryMap[factory0];
            }

            //clear renderers from Dictionary with strong references
            for (var renderer2:Object in measuringObjects)
            {
                clearRenderer(renderer2);
                delete measuringObjects[renderer2];
            }

            //clear renderers from dynamic object
            for (var renderer3:String in rowMap)
            {
                clearRenderer(rowMap[renderer3]);
                delete rowMap[renderer3];
            }

            highlightIndicator = null; //reset DisplayObject reference
            caretItemRenderer = null; //reset DisplayObject reference

            //clear border reference
            if (border)
            {
                if (border.parent == this)
                    this.removeChild(DisplayObject(border));
                //assume HaloBorder is used, change it in other case
                HaloBorder(border).styleName = null;
                border = null;
            }

            this.labelFunction = null;//Function reference MUST be cleared
            this.iconFunction = null;//Function reference MUST be cleared

            this.mx_internal::lastHighlightItemRenderer = null;
            this.mx_internal::lastHighlightItemRendererAtIndices = null;

            if (itemRenderer is IDisposable)
            {
                //clear all renderers created by itemRenderer.newInstance(), assume DisposableItemRenderer is used
                IDisposable(itemRenderer).dispose();
            }

            itemRenderer = null; //clear factory references
            itemEditor = null; //clear factory references, probably DisposableItemEditor must be used
            dataDescriptor = null;

            iterator = null;
            collection = null;
            this.mx_internal::wrappedCollection = null;
            this.mx_internal::collectionIterator = null;

            //reset DisplayObject reference
            if (maskShape)
            {
                if (maskShape.parent == this)
                {
                    removeChild(maskShape);
                }
                maskShape = new Shape();
            }
            //reset DisplayObject reference
            if (selectionLayer)
            {
                if (selectionLayer.parent == this)
                {
                    removeChild(selectionLayer);
                }
                selectionLayer = new Sprite();
            }

            disposed = true;
        }

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

        protected function clearRenderer(obj:Object):void
        {
            if (obj is UIComponent)
            {
                UIComponent(obj).owner = null; //clear possible parent DisplayObject reference
                UIComponent(obj).mx_internal::_parent = null;//clear evil mx_internal::_parent refrernces
                UIComponent(obj).mx_internal::_document = null;//clear evil mx_internal::_document refrernces
                UIComponent(obj).styleName = null;//styleName can be String or any Object so it must be reset

                if (obj is Canvas && Canvas(obj).mx_internal::contentPane)
                {
                    Canvas(obj).mx_internal::contentPane = null; //clear Canvas internal container reference.
                }

                if (obj is IDisposable && !(IDisposable(obj).isDisposed()))
                {
                    IDisposable(obj).dispose();
                }
            }
        }

        /**
         * Override to prevent callLater errors (fired by method executed using UIComponent.callLater())
         * @private
         */
        protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
        {
            if (!disposed)
                super.updateDisplayList(unscaledWidth, unscaledHeight);
        }
    }
}

Как видно, в дереве используется много динамических объектов, словарей с сильными ссылками, массивов, ссылок на дочерние и родительские объекты дисплейлиста, все это ставит сборщик мусора в тупик. Обратите внимание на перекрытый метод updateDisplayList, это сделано для того чтобы избежать ошибок при выполнении методов(в нашем случае это только updateDisplayList, но могут быть и другие) через механизм callLater. Об этих ошибках я может быть расскажу подробнее, в целом ситуация такова, что try/catch в этом случае не помогут (даже где-то есть официальный баг).

DisposableItemRenderer:

package gc
{
    import flash.display.DisplayObject;
    import flash.utils.Dictionary;

    import mx.controls.listClasses.BaseListData;
    import mx.controls.treeClasses.TreeItemRenderer;
    import mx.controls.treeClasses.TreeListData;
    import mx.core.IFactory;
    import mx.core.UIComponent;
    import mx.core.mx_internal;

    /**
     * TreeItemRenderer optimized for garbage collection.
     */
    public class DisposableItemRenderer extends TreeItemRenderer implements IDisposable, IFactory
    {
        /**
         * Created renderer instances, weakKeys=true
         */
        protected var factoryCache:Dictionary = new Dictionary(true);

        /**
         * Created ListData, weakKeys=true
         */
        protected var treeDataCache:Dictionary = new Dictionary(true);

        protected var disposed:Boolean;

        public function dispose():void
        {
            //force destroy private renderer listOwner property
            var treeListData:TreeListData = new TreeListData("", "", null);
            treeListData.disclosureIcon = null;
            treeListData.icon = null;
            this.data = {};
            super.listData = treeListData;

            //clear icon reference
            if (icon)
            {
                clearRenderer(icon);
                if(this.contains(DisplayObject(icon)))
                    removeChild(DisplayObject(icon));
                icon = null;
            }
            //clear icon reference
            if (disclosureIcon)
            {
                clearRenderer(disclosureIcon);
                if(this.contains(DisplayObject(disclosureIcon)))
                    removeChild(DisplayObject(disclosureIcon));
                disclosureIcon = null;
            }
            //clear textfield reference
            if (label != null)
            {
                clearRenderer(label);
                if(this.contains(DisplayObject(label)))
                    removeChild(DisplayObject(label));
                label = null;
            }

            //the only way to get private listOwner clean
            try
            {
                commitProperties();
            }
            catch(e:Error)
            {
                //ignore error
            }

            //clear created renderers
            for (var row:Object in factoryCache)
            {
                clearRenderer(row);
                if (row is IDisposable)
                {
                    IDisposable(row).dispose();
                }
                delete factoryCache[row];
            }

            //clear created ListData objects
            for (var data:Object in treeDataCache)
            {
                TreeListData(data).disclosureIcon = null;//clear icon reference
                TreeListData(data).icon = null;//clear icon reference
                TreeListData(data).item = null;
                TreeListData(data).owner = null;//clear possible parent reference
                delete treeDataCache[data];
            }
            clearRenderer(this);
            disposed = true;
        }

        /**
         * Override to save reference to TreeListData as a weak key in treeDataCache
         * @private
         */
        public override function set listData(value:BaseListData):void
        {
            treeDataCache[TreeListData(value)] = "added";
            super.listData = TreeListData(value);
        }

        /**
         * Override to prevent callLater errors
         */
        protected override function measure():void
        {
            if (!disposed)
            {
                super.measure();
            }
        }

        /**
         * Override to prevent callLater errors
         */
        protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
        {
            if (!disposed)
            {
                super.updateDisplayList(unscaledWidth, unscaledHeight);
            }
        }

        /**
         * Override to prevent callLater errors
         */
        protected override function commitProperties():void
        {
            if (!disposed)
            {
                super.commitProperties();
            }
        }

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

        protected function clearRenderer(obj:Object):void
        {
            if (obj is UIComponent)
            {
                UIComponent(obj).owner = null; //clear possible parent DisplayObject reference
                UIComponent(obj).mx_internal::_parent = null;//clear evil mx_internal::_parent refrernces
                UIComponent(obj).mx_internal::_document = null;//clear evil mx_internal::_document refrernces
                UIComponent(obj).styleName = null;//styleName can be String or any Object so it must be reset

                if (obj is Canvas && Canvas(obj).mx_internal::contentPane)
                {
                    Canvas(obj).mx_internal::contentPane = null; //clear Canvas internal container reference.
                }

                if (obj is IDisposable && !(IDisposable(obj).isDisposed()))
                {
                    IDisposable(obj).dispose();
                }
            }
        }

        /**
         * Creates an instance of some class (determined by the class that
	     * implements IFactory).
         * Also stores reference to created object as a weak key in factoryCache.
         * @return The newly created instance.
         */
        public function newInstance():*
        {
            var treeItemRenderer:DisposableItemRenderer = new DisposableItemRenderer();
            factoryCache[treeItemRenderer] = "added";
            return treeItemRenderer;
        }
    }
}

Такая же ситуация и с рендерером, наибольшую проблему представляет ссылка на дерево — listOwner, ее очищаем фактически через хак. Создается большое количество объектов TreeListData, которые содержат паразитные ссылки. Так же 3 метода надо перекрыть чтобы предотвратить ошибки во время выполнения их через механизм callLater. Используем кэш со слабыми ссылками для того, чтобы очистить все созданные методом newInstance():* объекты.
Когда работа с деревом завершена просто вызываем tree.dispose() и все будет очищено. Подобные вещи придется проделать и с другими классами наследующими ListBase. Проверка профайлером показала, что DisposableTree собирается как надо.

, , , ,

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