Метатеги времени исполнения.
Очень часто, общаясь с коллегам, я замечаю, что многие весьма поверхностно знакомы с возможностями использования метатегов в ActionScript 3.0. Конечно большинству эта тема будет уже не интересна, однако многие живут годами, не зная о такой мощной фиче AS3 как метатеги. Ну или не зная всех возможностей . Именно для этих людей и предназначается данная статья.
Код изложенный в этой статье максимально прост, и написан как можно более кратко, что бы объяснить основную концепцию. Но для практических задач он неприемлем, так как в нем не учтены многие ньюансы. В следующей статье, мы придадим ему более полный вид.
Итак начнем:
Для начала определим три вида метатегов:
Метатеги времени компиляции:
Это по сути инструкции компилятору, чаще всего они указывают, что код необходимо преобразовать. Самый известный пример - [Bindable]. Подробнее об этом метатеге можно почитать тут: http://blog.diestro.ru/binding-v-actionscript-1/
Метатеги - инструкции для IDE:
Данные метатеги никак не влияют на приложение. Они исключительно помогают кодогенерации и другим фичам IDE, так же некоторые из них (например [Deprecated]) вызавут ворнинг во время компиляции, при использовании свойств отмеченных ими. Самые известные:
[Exclude] - удаляет свойство из MXML автокомплита.
[Inspectable] - позвотяет описать возможные значения которые принимает сеттер. Крайне удобен при работе с MXML.
Метатеги времени исполнения:
Так же существует возможноть пометить нейкое свойство, метод или весь класс метатегом, что бы использовать метатег как инструкцию для каких либо действий. Существует много фреймворков, позваляюших осуществлять доступ к метатегам, однако мы расмотрим этот вопрос, так сказать, на низком уровне.
Давайте расмотрим следующий пример:
package view { import flash.display.Sprite; import flash.events.Event; import flash.text.TextField; import flash.text.TextFormat; /** * ... * @author vaukalak */ public class TimerView extends Sprite { private var _textField:TextField; public function TimerView() { _textField = new TextField(); _textField.defaultTextFormat = new TextFormat(null, 30); addChild(_textField); } [EnterFrame] public function update(event:Event):void { _textField.text = String(uint(_textField.text) + 1); } } }
Во первых: нам нужно добавить в доп. опции компилятора следующую строчку:
Код:
-keep-as3-metadata+=EnterFrame
Во втрорых: нам нужен механизм, который проанализирует наш класс и увидев в его описании метатег EnterFrame, сделает то что нам нужно.
Для того, что бы получить описание класса в формате XML, нужно написать следующий код:
Вот как выглядит описание нашего класса:
Код:
<type name="view::TimerView" base="flash.display::Sprite" isDynamic="false" isFinal="false" isStatic="false"> <extendsClass type="flash.display::Sprite"/> <extendsClass type="flash.display::DisplayObjectContainer"/> <extendsClass type="flash.display::InteractiveObject"/> <extendsClass type="flash.display::DisplayObject"/> <extendsClass type="flash.events::EventDispatcher"/> <extendsClass type="Object"/> <implementsInterface type="flash.events::IEventDispatcher"/> <implementsInterface type="flash.display::IBitmapDrawable"/> <accessor name="visible" access="readwrite" type="Boolean" declaredBy="flash.display::DisplayObject"/> <accessor name="scaleZ" access="readwrite" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="rotation" access="readwrite" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="mouseY" access="readonly" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="rotationX" access="readwrite" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="mouseX" access="readonly" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="rotationY" access="readwrite" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="opaqueBackground" access="readwrite" type="Object" declaredBy="flash.display::DisplayObject"/> <accessor name="scrollRect" access="readwrite" type="flash.geom::Rectangle" declaredBy="flash.display::DisplayObject"/> <accessor name="cacheAsBitmap" access="readwrite" type="Boolean" declaredBy="flash.display::DisplayObject"/> <accessor name="name" access="readwrite" type="String" declaredBy="flash.display::DisplayObject"/> <accessor name="tabChildren" access="readwrite" type="Boolean" declaredBy="flash.display::DisplayObjectContainer"/> <accessor name="rotationZ" access="readwrite" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="scaleX" access="readwrite" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="mouseChildren" access="readwrite" type="Boolean" declaredBy="flash.display::DisplayObjectContainer"/> <accessor name="transform" access="readwrite" type="flash.geom::Transform" declaredBy="flash.display::DisplayObject"/> <accessor name="x" access="readwrite" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="scale9Grid" access="readwrite" type="flash.geom::Rectangle" declaredBy="flash.display::DisplayObject"/> <accessor name="blendMode" access="readwrite" type="String" declaredBy="flash.display::DisplayObject"/> <accessor name="z" access="readwrite" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="scaleY" access="readwrite" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="filters" access="readwrite" type="Array" declaredBy="flash.display::DisplayObject"/> <accessor name="y" access="readwrite" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="soundTransform" access="readwrite" type="flash.media::SoundTransform" declaredBy="flash.display::Sprite"/> <accessor name="loaderInfo" access="readonly" type="flash.display::LoaderInfo" declaredBy="flash.display::DisplayObject"/> <accessor name="hitArea" access="readwrite" type="flash.display::Sprite" declaredBy="flash.display::Sprite"/> <accessor name="dropTarget" access="readonly" type="flash.display::DisplayObject" declaredBy="flash.display::Sprite"/> <accessor name="buttonMode" access="readwrite" type="Boolean" declaredBy="flash.display::Sprite"/> <accessor name="accessibilityProperties" access="readwrite" type="flash.accessibility::AccessibilityProperties" declaredBy="flash.display::DisplayObject"/> <accessor name="numChildren" access="readonly" type="int" declaredBy="flash.display::DisplayObjectContainer"/> <accessor name="textSnapshot" access="readonly" type="flash.text::TextSnapshot" declaredBy="flash.display::DisplayObjectContainer"/> <accessor name="useHandCursor" access="readwrite" type="Boolean" declaredBy="flash.display::Sprite"/> <accessor name="blendShader" access="writeonly" type="flash.display::Shader" declaredBy="flash.display::DisplayObject"/> <accessor name="tabEnabled" access="readwrite" type="Boolean" declaredBy="flash.display::InteractiveObject"/> <accessor name="tabIndex" access="readwrite" type="int" declaredBy="flash.display::InteractiveObject"/> <accessor name="width" access="readwrite" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="height" access="readwrite" type="Number" declaredBy="flash.display::DisplayObject"/> <accessor name="doubleClickEnabled" access="readwrite" type="Boolean" declaredBy="flash.display::InteractiveObject"/> <accessor name="focusRect" access="readwrite" type="Object" declaredBy="flash.display::InteractiveObject"/> <accessor name="parent" access="readonly" type="flash.display::DisplayObjectContainer" declaredBy="flash.display::DisplayObject"/> <accessor name="mouseEnabled" access="readwrite" type="Boolean" declaredBy="flash.display::InteractiveObject"/> <accessor name="accessibilityImplementation" access="readwrite" type="flash.accessibility::AccessibilityImplementation" declaredBy="flash.display::InteractiveObject"> <metadata name="Inspectable"> <arg key="environment" value="none"/> </metadata> </accessor> <accessor name="contextMenu" access="readwrite" type="flash.ui::ContextMenu" declaredBy="flash.display::InteractiveObject"/> <accessor name="graphics" access="readonly" type="flash.display::Graphics" declaredBy="flash.display::Sprite"/> <accessor name="root" access="readonly" type="flash.display::DisplayObject" declaredBy="flash.display::DisplayObject"/> <accessor name="stage" access="readonly" type="flash.display::Stage" declaredBy="flash.display::DisplayObject"/> <accessor name="mask" access="readwrite" type="flash.display::DisplayObject" declaredBy="flash.display::DisplayObject"/> <accessor name="alpha" access="readwrite" type="Number" declaredBy="flash.display::DisplayObject"/> <method name="addEventListener" declaredBy="flash.events::EventDispatcher" returnType="void"> <parameter index="1" type="String" optional="false"/> <parameter index="2" type="Function" optional="false"/> <parameter index="3" type="Boolean" optional="true"/> <parameter index="4" type="int" optional="true"/> <parameter index="5" type="Boolean" optional="true"/> </method> <method name="dispatchEvent" declaredBy="flash.events::EventDispatcher" returnType="Boolean"> <parameter index="1" type="flash.events::Event" optional="false"/> </method> <method name="removeEventListener" declaredBy="flash.events::EventDispatcher" returnType="void"> <parameter index="1" type="String" optional="false"/> <parameter index="2" type="Function" optional="false"/> <parameter index="3" type="Boolean" optional="true"/> </method> <method name="willTrigger" declaredBy="flash.events::EventDispatcher" returnType="Boolean"> <parameter index="1" type="String" optional="false"/> </method> <method name="addChildAt" declaredBy="flash.display::DisplayObjectContainer" returnType="flash.display::DisplayObject"> <parameter index="1" type="flash.display::DisplayObject" optional="false"/> <parameter index="2" type="int" optional="false"/> </method> <method name="hasEventListener" declaredBy="flash.events::EventDispatcher" returnType="Boolean"> <parameter index="1" type="String" optional="false"/> </method> <method name="removeChild" declaredBy="flash.display::DisplayObjectContainer" returnType="flash.display::DisplayObject"> <parameter index="1" type="flash.display::DisplayObject" optional="false"/> </method> <method name="removeChildAt" declaredBy="flash.display::DisplayObjectContainer" returnType="flash.display::DisplayObject"> <parameter index="1" type="int" optional="false"/> </method> <method name="getChildIndex" declaredBy="flash.display::DisplayObjectContainer" returnType="int"> <parameter index="1" type="flash.display::DisplayObject" optional="false"/> </method> <method name="toString" declaredBy="flash.events::EventDispatcher" returnType="String"/> <method name="setChildIndex" declaredBy="flash.display::DisplayObjectContainer" returnType="void"> <parameter index="1" type="flash.display::DisplayObject" optional="false"/> <parameter index="2" type="int" optional="false"/> </method> <method name="getChildAt" declaredBy="flash.display::DisplayObjectContainer" returnType="flash.display::DisplayObject"> <parameter index="1" type="int" optional="false"/> </method> <method name="swapChildrenAt" declaredBy="flash.display::DisplayObjectContainer" returnType="void"> <parameter index="1" type="int" optional="false"/> <parameter index="2" type="int" optional="false"/> </method> <method name="swapChildren" declaredBy="flash.display::DisplayObjectContainer" returnType="void"> <parameter index="1" type="flash.display::DisplayObject" optional="false"/> <parameter index="2" type="flash.display::DisplayObject" optional="false"/> </method> <method name="globalToLocal" declaredBy="flash.display::DisplayObject" returnType="flash.geom::Point"> <parameter index="1" type="flash.geom::Point" optional="false"/> </method> <method name="startTouchDrag" declaredBy="flash.display::Sprite" returnType="void"> <parameter index="1" type="int" optional="false"/> <parameter index="2" type="Boolean" optional="true"/> <parameter index="3" type="flash.geom::Rectangle" optional="true"/> </method> <method name="getBounds" declaredBy="flash.display::DisplayObject" returnType="flash.geom::Rectangle"> <parameter index="1" type="flash.display::DisplayObject" optional="false"/> </method> <method name="getObjectsUnderPoint" declaredBy="flash.display::DisplayObjectContainer" returnType="Array"> <parameter index="1" type="flash.geom::Point" optional="false"/> </method> <method name="getRect" declaredBy="flash.display::DisplayObject" returnType="flash.geom::Rectangle"> <parameter index="1" type="flash.display::DisplayObject" optional="false"/> </method> <method name="localToGlobal" declaredBy="flash.display::DisplayObject" returnType="flash.geom::Point"> <parameter index="1" type="flash.geom::Point" optional="false"/> </method> <method name="areInaccessibleObjectsUnderPoint" declaredBy="flash.display::DisplayObjectContainer" returnType="Boolean"> <parameter index="1" type="flash.geom::Point" optional="false"/> </method> <method name="stopTouchDrag" declaredBy="flash.display::Sprite" returnType="void"> <parameter index="1" type="int" optional="false"/> </method> <method name="hitTestObject" declaredBy="flash.display::DisplayObject" returnType="Boolean"> <parameter index="1" type="flash.display::DisplayObject" optional="false"/> </method> <method name="hitTestPoint" declaredBy="flash.display::DisplayObject" returnType="Boolean"> <parameter index="1" type="Number" optional="false"/> <parameter index="2" type="Number" optional="false"/> <parameter index="3" type="Boolean" optional="true"/> </method> <method name="getChildByName" declaredBy="flash.display::DisplayObjectContainer" returnType="flash.display::DisplayObject"> <parameter index="1" type="String" optional="false"/> </method> <method name="startDrag" declaredBy="flash.display::Sprite" returnType="void"> <parameter index="1" type="Boolean" optional="true"/> <parameter index="2" type="flash.geom::Rectangle" optional="true"/> </method> <method name="globalToLocal3D" declaredBy="flash.display::DisplayObject" returnType="flash.geom::Vector3D"> <parameter index="1" type="flash.geom::Point" optional="false"/> </method> <method name="stopDrag" declaredBy="flash.display::Sprite" returnType="void"/> <method name="local3DToGlobal" declaredBy="flash.display::DisplayObject" returnType="flash.geom::Point"> <parameter index="1" type="flash.geom::Vector3D" optional="false"/> </method> <method name="update" declaredBy="view::TimerView" returnType="void"> <parameter index="1" type="flash.events::Event" optional="false"/> <metadata name="EnterFrame"/> <metadata name="__go_to_definition_help"> <arg key="pos" value="469"/> </metadata> </method> <method name="contains" declaredBy="flash.display::DisplayObjectContainer" returnType="Boolean"> <parameter index="1" type="flash.display::DisplayObject" optional="false"/> </method> <method name="addChild" declaredBy="flash.display::DisplayObjectContainer" returnType="flash.display::DisplayObject"> <parameter index="1" type="flash.display::DisplayObject" optional="false"/> </method> <metadata name="__go_to_ctor_definition_help"> <arg key="pos" value="290"/> </metadata> <metadata name="__go_to_definition_help"> <arg key="pos" value="199"/> </metadata> </type>
Код:
<method name="update" declaredBy="view::TimerView" returnType="void"> <parameter index="1" type="flash.events::Event" optional="false"/> <metadata name="EnterFrame"/> <metadata name="__go_to_definition_help"> <arg key="pos" value="469"/> </metadata> </method>
По сути, мы получаем описание объекта, пробегаем по методам, выбираем те, что помечены метатегом EnterFrame, и забираем их имя.
Это была самая сложная часть
Вот небольшой пример нашего менеджера:
package reflection { import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.events.Event; import flash.events.IEventDispatcher; import flash.utils.describeType; import flash.utils.Dictionary; /** * ... * @author vaukalak */ public class EnterFrameHelper { private const _linker:Dictionary = new Dictionary(true); public static const cache:Dictionary = new Dictionary(); public function startOn(container:DisplayObjectContainer):void { //будем проверять все объекты, которые добавляются в container, или в его детей. container.addEventListener(Event.ADDED, _onChildAdded, true); } private function _onChildAdded(event:Event):void { var targetDO:DisplayObject = event.target as DisplayObject; //Массив методов помеченных EnterFrame var handlers:Vector.<Function> = new Vector.<Function>(); //находим тип нашего объекта и кешируем его описание. //метод describeType весьма прожерливый, так что кешировать его обязательно. var objectClass:Class = Object(event.target).constructor; cache[objectClass] ||= describeType(event.target); //находим все методы, помеченные метатегом EnterFrame //и пихаем их в массив обработчиков cache[objectClass].method.(valueOf().metadata.(@name == "EnterFrame").length()).(handlers.push(targetDO[@name])); if (handlers.length) { //если обработчики есть, то как только объект добавиться на сцену, //сразу подпишем его на ентерфрейм targetDO.addEventListener(Event.ADDED_TO_STAGE, _onTargetAddedToStage, false, 0, true); targetDO.addEventListener(Event.REMOVED_FROM_STAGE, _onTargetRemovedFromStage, false, 0, true); _linker[targetDO] = handlers; } } private function _onTargetRemovedFromStage(event:Event):void { (event.target as DisplayObject).removeEventListener(Event.ENTER_FRAME, _onEnterFrame); } private function _onTargetAddedToStage(event:Event):void { (event.target as DisplayObject).addEventListener(Event.ENTER_FRAME, _onEnterFrame); } private function _onEnterFrame(event:Event):void { for each(var handler:Function in _linker[event.target]) { handler(event); } } } }
package { import flash.display.Sprite; import flash.events.Event; import flash.utils.describeType; import reflection.EnterFrameHelper; import view.TimerView; import view.TimerViewWithoutReflection; /** * ... * @author vaukalak */ public class Main extends Sprite { private var _enterFrameHelper:EnterFrameHelper; public function Main():void { _enterFrameHelper = new EnterFrameHelper(); _enterFrameHelper.startOn(stage); //будем описывать все, что попадет на сцену. addChild(new TimerView()); } } }
package view { import flash.display.Sprite; import flash.events.Event; import flash.text.TextField; import flash.text.TextFormat; /** * ... * @author vaukalak */ public class TimerViewWithoutReflection extends Sprite{ private var _textField:TextField; public function TimerViewWithoutReflection() { _textField = new TextField(); _textField.defaultTextFormat = new TextFormat(null, 30); addChild(_textField); addEventListener(Event.ADDED_TO_STAGE, _onAddedToStage, false, 0, true); addEventListener(Event.REMOVED_FROM_STAGE, _onRemovedFromStage, false, 0, true); } private function _onRemovedFromStage(e:Event):void { removeEventListener(Event.ENTER_FRAME, _onEnterFrame); } private function _onAddedToStage(e:Event):void { addEventListener(Event.ENTER_FRAME, _onEnterFrame); } private function _onEnterFrame(e:Event):void { _textField.text = String(uint(_textField.text) + 1); } } }
Всего комментариев 21
Комментарии
16.12.2012 18:10 | |
Оу, в примере столько мороки, что преимущества совсем не видать. Но в целом интересно.
|
16.12.2012 19:16 | |
Тема хорошая, только применяется не там, где надо.
|
16.12.2012 20:22 | |
Денис, я, надеюсь, это будет во второй части
|
16.12.2012 22:06 | |
прицепить можно, а что конкретно с ним делать?
|
17.12.2012 16:48 | |
Этот пример понятен, но он также нарочито показывает лень разработчика.
|
17.12.2012 19:12 | |
Отлично! Интересно, спасибо :3
|
26.12.2012 23:17 | |
А чего раскрывать тему, тут в примере типа показываем, что нам лень отписываться (но не лень подписываться). Честное слово, пример с десериализацией чего-нибудь и тот интересней.
|
Последние записи от incvizitor
- Метатеги времени исполнения. (15.12.2012)