User's collector

Внимание!   Данная опция будет доступна только после того, как вы авторизуетесь.
   запомнить меня 

Предлагаю вашему вниманию небольшой трюк по оптимизации Flex приложений.

Часто бывает необходимым наделить свой класс возможностью рассылать события, однако мы почти уверены, что подписываться на эти события объекты-получатели будут крайне редко. Поэтому не хочется наследовать в своем классе весь функционал flash.event.EventDispatcher и запускать «без дела» в коде своего класса распространение событий вызовом метода dispachEvent().

Как раз на этот случай во Flex 4 framework припасен утилитный класс mx.utils.OnDemandEventDispatcher, позволяющий улучшить производительность нашего приложения по двум направлениям:

  • OnDemandEventDispatcher не является наследником EventDispatcher, а только лишь реализует интерфейс IEventDispatcher. Класс посредством композиции делегирует выполнение методов, объявленных в интерфейсе, внутреннему экземпляру EventDispatcher. Сам же внутренний объект-диспетчер создается только в случае необходимости — при первой подписке на рассылку событий от текущего объекта. Таким образом, удается немного сэкономить занимаемый приложением объем оперативной памяти.
  • Если еще ни один объект не подписывался на получение событий, то делегирование методов внутреннему объекту-диспетчеру не производится. Тем самым, глубина стека выполнения функций ограничивается методами объекта-диспетчера. Это хорошо иллюстрирует листинг, взятый из класса OnDemandEventDispatcher:
    Actionscript:
    1. public function dispatchEvent(event:Event):Boolean
    2. {
    3.     if(_dispatcher != null)
    4.         return _dispatcher.dispatchEvent(event);
    5.     return true;
    6. }

Таким вот нехитрым образом, наследуясь там, где это необходимо, от класса OnDemandEventDispatcher мы хоть и ненамного, но все же улучшаем производительность нашего Flex приложения.

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

Теги:

Тот, кто работал с Flex Framework, обязательно должен был столкнуться в нем с такой замечательной функциональностью, как связывание данных (Data Binding). Наверняка, есть разработчики, желающие более глубоко изучить эту тему. И тут, как нельзя кстати, будет статья «Flex Data Binding Tricks» на русском языке от пользователя oss на Хабрахабре.

В этой статье очень подробно рассматриваются методы работы со связыванием данных при помощи ActionScrit 3. Так что, удачного вам чтения. И еще: не называйте, пожалуйста, байндинг биндингом. =)

Один комментарий

Теги:

В одном из разделов форума Flasher.ru был задан вопрос о том, каким образом нужно правильно удалять всех детей из объекта DisplayObjectContainer.

В ходе обсуждения были предложены следующие варианты (орфография и пунктуация авторов сохранены =):

  1. Вариант пользователя smilenka.
    Actionscript:
    1. var maxChildIndex:int = graphicContext.numChildren - 1 ;
    2. for( var childIndex:int = maxChildIndex; childIndex>= 0 ; childIndex-- )
    3. {
    4.     graphicContext.removeChildAt( childIndex ) ;
    5. }

  2. Вариант пользователя __etc.
    Actionscript:
    1. var l:uint = super.numChildren;
    2. while (l--) super.removeChildAt(l);

  3. Вариант пользователя wvxvw.
    Actionscript:
    1. while (numChildren) removeChildAt(0);

Так какой же из вариантов самый оптимальный?

Внимание, правильный ответ. В плане быстродействия лидирует вариант номер три от пользователя wvxvw (761 миллисекунд против 1757, получаемых во втором варианте при 10000 итерациях):

Actionscript:
  1. var s:Sprite = new Sprite();
  2. for (var i:uint = 0; i <10000; i ++) {
  3.     var h:Sprite = new Sprite();
  4.     s.addChild(h);
  5. }
  6.            
  7. var t1:Number = getTimer();  
  8. var l:uint = s.numChildren;
  9. while (l--) s.removeChildAt(l);  
  10. var t2:Number = getTimer();  
  11. Debugger.trace(t2 - t1);//1575
  12.            
  13. //***********************************
  14.            
  15. var b:Sprite = new Sprite();
  16. for (var j:uint = 0; j <10000; j ++) {
  17.     var m:Sprite = new Sprite();
  18.     b.addChild(m);
  19. }
  20.            
  21. var t3:Number = getTimer();  
  22. while (b.numChildren) b.removeChildAt(0);      
  23. var t4:Number = getTimer();
  24.                    
  25. Debugger.trace(t4 - t3);//761

Коментарии по этому поводу от Дениса «__etc» Коляко:

«Разница в том, что код wvxvw в цикле запрашивает геттер numChildren и удаляет ребенка с нулевого индекса.

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

Как выяснилось, в модели DisplayObjectContainer элемент на нулевом индексе оказывается последним в массиве элементов, а не наоборот. Именно поэтому удаление последнего элемента в display list медленнее, чем первого. Ну а геттер numChildren оказался таким же быстрым, как и декремент локальной переменной, вероятно в силу того, что декремент выполяется в несколько действий, с конвертацией и прочим.»

Посмотрим, как метод удаления всех детей реализован во Flex Framework:

Actionscript:
  1. public function removeAllChildren():void
  2. {
  3.     while (numChildren> 0)
  4.     {
  5.         removeChildAt(0);
  6.     }
  7. }

По всей видимости, над этим фреймворком работали не такие уж и бестолковые программисты… ;-)

На этом все. Эффективного вам кода!

15 комментариев

Теги:

Не секрет, что ActionScript 3 не поддерживает перегрузки методов. Иногда это может привести к затруднительным ситуациям. Например, у нас есть два интрефейса, в которых объявлены одноименные методы, но с различной сигнатурой, и требуется создать класс, который должен реализовывать оба этих интерфейса. При попытке реализовать классом оба таких интерфейса мы совершенно законно столкнемся с ошибкой. Чтобы быть более конкретным приведу пример.

У нас есть два интерфейса IHuman и IMonster, в которых объявлен метод kill, только в первом случае метод в качестве аргумента принимает IMonster-жертву, а во втором — в роли жертвы должен выступать IHuman. При попытке создать класс Werewolf, который должен являться как и IHuman, так и IMonster мы окажемся в затруднительной ситуации, поскольку как я уже сказал ранее ActionScript 3 не поддерживает перегрузки методов.

В сложившейся ситуации у нас есть два варианта наших действий. Первый вариант предполагает изменение условий задачи, проще говоря мы можем переименовать методы, например killMonster для IHuman и killHuman для IMonster. Если у вас есть возможность пойти по этому пути, то можете не колебаться и смело выбирать его. Второй вариант, исходит из того, что вы не можете изменить имена методов. В этом случае, можно создать два отдельных класса, каждый из которых реализует необходимый интерфейс и использовать их вместе в составе третьего класса Werewolf. Здесь есть маленькая тонкость. Как между двумя этими классами расшарить внутреннюю логику Werewolf. Например, в методе kill этих классов вызвать приватный метод prepareToKill класса Werewolf. В этой ситуации я для себя нашел небольшой workaround — создание класса оборотня.

Для начала нам потребуются выше обзначенные интерфейсы.

Actionscript:
  1. package
  2. {
  3.     public interface IHuman
  4.     {
  5.         function kill (victim:IMonster):void;
  6.     }
  7. }

Actionscript:
  1. package
  2. {
  3.     public interface IMonster
  4.     {
  5.         function kill (victim:IHuman):void;
  6.     }
  7. }

Переходим к созданию класса Werewolf, содержащий в себе как класс реализующий IHuman, так и класс реализующий IMonster. Оба эти классы возможны только как составляющие Werewolf, соответственно их можно смело сделать вложенными, то есть объявить за скобками пакета в том же файле, что описывает собственно сам класс Werewolf.

Actionscript:
  1. package
  2. {
  3.     public class Werewolf
  4.     {
  5.         private var _monster:IMonster = null;
  6.         private var _human:IHuman = null;
  7.        
  8.         public function Werewolf()
  9.         {
  10.             _monster = new Monster();
  11.             _human = new Human();
  12.         }
  13.        
  14.         public function asHuman ():IHuman
  15.         {
  16.             return _human;
  17.         }
  18.        
  19.         public function asMonster ():IMonster
  20.         {
  21.             return _monster;
  22.         }
  23.     }
  24. }
  25.  
  26.  
  27. internal class Monster implements IMonster
  28. {   
  29.     public function Monster ()
  30.     {
  31.     }
  32.    
  33.     public function kill (victim:IHuman):void
  34.     {
  35.         trace("human killed");
  36.     }
  37. }
  38.  
  39. internal class Human implements IHuman
  40. {
  41.     public function Human ()
  42.     {
  43.     }
  44.    
  45.     public function kill (victim:IMonster):void
  46.     {
  47.         trace("monster killed");
  48.     }
  49. }

И так мы имеем класс Werewolf, который может быть использован как IMonster, так и IHuman, для этого необходимо вызвать соответсвующий метод. Теперь вернемся к условиям, а именно, кроме реализации классов Monster и Human, нам требуется расшарить для них внутреннюю логику Werewolf. Так как эти классы могут существовать только в составе Werewolf мы могли бы смело дать им возможность вызывать приватные методы Werewolf. Однако, как мы знаем приватный метод, он на то и приватный, чтобы быть доступным только в рамках своего класса. Но нам никто не мешает создать собственную область видимости. А помогут нам в этом пространтсва имен. Чтобы создать неймспейс, видимый только в пределах наших классов, объявим его как и классы Monster и Human вне пакета. Таким образом методы, объявленные в этом неймспейсе, могут быть вызваны, любым из наших трех классов.

Actionscript:
  1. package
  2. {
  3.     public class Werewolf implements IWerewolf
  4.     {
  5.         private var _monster:IMonster = null;
  6.         private var _human:IHuman = null;
  7.        
  8.         public function Werewolf()
  9.         {
  10.             super();
  11.             _monster = new Monster(this);
  12.             _human = new Human(this);
  13.         }
  14.        
  15.         werewolf_private function prepareToKill ():void
  16.         {
  17.             trace("prepared for kill");
  18.         }
  19.        
  20.         public function asHuman ():IHuman
  21.         {
  22.             return _human;
  23.         }
  24.        
  25.         public function asMonster ():IMonster
  26.         {
  27.             return _monster;
  28.         }
  29.     }
  30. }
  31.  
  32. internal namespace werewolf_private;
  33.  
  34. internal class Monster implements IMonster
  35. {
  36.     private var _owner:Werewolf = null;
  37.    
  38.     public function Monster (owner:Werewolf)
  39.     {
  40.         _owner = owner;
  41.     }
  42.    
  43.     public function kill (victim:IHuman):void
  44.     {
  45.         _owner.werewolf_private::prepareToKill();
  46.         trace("human killed");
  47.     }
  48. }
  49.  
  50. internal class Human implements IHuman
  51. {
  52.     private var _owner:Werewolf = null;
  53.    
  54.     public function Human (owner:Werewolf)
  55.     {
  56.         _owner = owner;
  57.     }
  58.    
  59.     public function kill (victim:IMonster):void
  60.     {
  61.         _owner.werewolf_private::prepareToKill();
  62.         trace("monster killed");
  63.     }
  64. }

Таким образом, мы получили метод prepareToKill видимый только в пределах наших классов. Аналогичным образом можно расшарить и прочую логику. На этом можно было бы и остановиться, но есть один момент, каждая из составляющих как Human, так и Monster не являются Werewolf-ом. Решается это достаточно просто. Введем интерфейс IWerewolf, где и обозначим соответствующий API.

Actionscript:
  1. package
  2. {
  3.     public interface IWerewolf
  4.     {
  5.         function asHuman ():IHuman;
  6.        
  7.         function asMonster ():IMonster;
  8.     }
  9. }

Теперь остается только реализовать этот интерфейс всеми тремя классами.

Actionscript:
  1. package
  2. {
  3.     public class Werewolf implements IWerewolf
  4.     {
  5.         private var _monster:IMonster = null;
  6.         private var _human:IHuman = null;
  7.        
  8.         public function Werewolf()
  9.         {
  10.             super();
  11.             _monster = new Monster(this);
  12.             _human = new Human(this);
  13.         }
  14.        
  15.         werewolf_private function prepareToKill ():void
  16.         {
  17.             trace("prepared for kill");
  18.         }
  19.        
  20.         public function asHuman ():IHuman
  21.         {
  22.             return _human;
  23.         }
  24.        
  25.         public function asMonster ():IMonster
  26.         {
  27.             return _monster;
  28.         }
  29.     }
  30. }
  31.  
  32. internal namespace werewolf_private;
  33.  
  34. internal class Monster implements IMonster, IWerewolf
  35. {
  36.     private var _owner:Werewolf = null;
  37.    
  38.     public function Monster (owner:Werewolf)
  39.     {
  40.         _owner = owner;
  41.     }
  42.    
  43.     public function kill (victim:IHuman):void
  44.     {
  45.         _owner.werewolf_private::prepareToKill();
  46.         trace("human killed");
  47.     }
  48.    
  49.     public function asHuman ():IHuman
  50.     {
  51.         return _owner.asHuman();
  52.     }
  53.    
  54.     public function asMonster ():IMonster
  55.     {
  56.         return this;
  57.     }
  58. }
  59.  
  60. internal class Human implements IHuman, IWerewolf
  61. {
  62.     private var _owner:Werewolf = null;
  63.    
  64.     public function Human (owner:Werewolf)
  65.     {
  66.         _owner = owner;
  67.     }
  68.    
  69.     public function kill (victim:IMonster):void
  70.     {
  71.         _owner.werewolf_private::prepareToKill();
  72.         trace("monster killed");
  73.     }
  74.    
  75.     public function asHuman ():IHuman
  76.     {
  77.         return this;
  78.     }
  79.    
  80.     public function asMonster ():IMonster
  81.     {
  82.         return _owner.asMonster();
  83.     }
  84. }

Проверим что у нас получилось.

Actionscript:
  1. package
  2. {
  3.     import flash.display.Sprite;
  4.  
  5.     public class World extends Sprite
  6.     {
  7.         public function World()
  8.         {
  9.             var werewolf:Werewolf = new Werewolf();
  10.            
  11.             // "Кастим" werewolf как IMonster и как IHuman
  12.             trace(werewolf.asMonster() is IMonster);
  13.             trace(werewolf.asHuman() is IHuman);
  14.            
  15.             // Проверяем является ли каждая составляющая IWerewolf
  16.             trace(werewolf.asMonster() is IWerewolf);
  17.             trace(werewolf.asHuman() is IWerewolf);
  18.            
  19.             // Ну а здесь уже мистика :)
  20.             trace((werewolf.asMonster() as IWerewolf).asHuman() is IHuman);
  21.             trace((werewolf.asHuman() as IWerewolf).asMonster() is IMonster);
  22.            
  23.             // Попробуем убить нашим оборотнем как человека так и монстра
  24.             var knight:Knight = new Knight();
  25.             var zombie:Zombie = new Zombie();
  26.            
  27.             werewolf.asMonster().kill(knight);
  28.             werewolf.asHuman().kill(zombie);
  29.         }
  30.     }
  31. }

Напоследок напомню, при возможности изменить условия, а именно устранить конфликт путем смены сигнатуры методов, не раздумывайте и выбирайте этот вариант. Описанный прием является скорее крайней мерой и способом сделать код менее прозрачным.

15 комментариев

Теги:

Начну с обычного примера:

Actionscript:
  1. package
  2. {
  3.     public class XMLExample
  4.     {
  5.         public function XMLExample ()
  6.         {
  7.             var someXml:XML =
  8.                 <root>
  9.                     <item id="id" content-type="image/png">image
  10.                 </root>;
  11.         }
  12.     }
  13. }

Получить значение аттрибута id тэга item можно так:

Actionscript:
  1. trace(someXml.item.@id);

Значения аттрибута content-type содержит символ "-" поэтому мы используем такую конструкцию:

Actionscript:
  1. trace(someXml.item.@["content-type"]);

Все это хорошо до тех пор, пока в xml не появляется xmlns, из-за которого приведенный выше код перестает работать на вполне законных основаниях:

Actionscript:
  1. var someXml:XML =
  2.         <root xmlns="http://uri">
  3.             <item id="id" content-type="image/png">image</item>
  4.         </root>;

Обратившись к первоисточнику я узнал, что класс верхнего уровня Namespace мне поможет:

Actionscript:
  1. package
  2. {
  3.     public class XMLExample
  4.     {
  5.         private var ns:Namespace = new Namespace("http://uri");
  6.        
  7.         public function XMLExample ()
  8.         {
  9.             var someXml:XML =
  10.                 <root xmlns="http://uri">
  11.                     <item id="id" content-type="image/png">image</item>
  12.                 </root>;
  13.            
  14.             default xml namespace = ns;
  15.  
  16.             // Вот это прекрасно работает
  17.             trace(someXml.item.@id);
  18.            
  19.             <strong>// А вот для content-type работать отказывается
  20.             trace(someXml.item.@["content-type"]);
  21.            
  22.             // И так тоже результат нулевой
  23.             trace(someXml.item.attribute("content-type"));</strong>
  24.         }
  25.     }
  26. }

Хорошо, подумал я и решил проверить видны ли атрибуты в принципе:

Actionscript:
  1. var l:int = someXml.item.attributes().length();
  2. for(var i:int = 0; i <l; i++)
  3.     trace("Attribute #" + i + ": " + someXml.item.attributes()[i].name());

Трэйсы исправно вывели:

CODE:
  1. Attribute #0: id
  2. Attribute #1: content-type

Ага! — сказал я и решил попробывать следующую строку:

Actionscript:
  1. trace(someXml.item.@[someXml.item.attributes()[1].name()]);

И как вы наверно догадались этот трейс выводит: image/png.

Снова обращаюсь к первоисточнику в попытках узнать, что же возвращает такое name(), что это открывает мне доступ к значению необходимого аттрибута. Согласно сигнатуре и описанию данный метод возвращает самый обычный Object. Или нет? Вот цитата:

Returns
Object - qualified name is either a QName or null.

Быстренько прокручиваю к разделу See also и нахожу в нем линк на еще один класс верхнего уровня Qname (все таки asdoc классная документация), который и стал решением моей проблемы. И так внимание правильный ответ:

Actionscript:
  1. trace(someXml.item.@[new QName("", "content-type")]);

Вывод: чтение документации требует максимальной внимательности и предельной концентрации.

Удачи! ;)

5 комментариев

Теги: