Метатеги времени исполнения.
Очень часто, общаясь с коллегам, я замечаю, что многие весьма поверхностно знакомы с возможностями использования метатегов в 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
Комментарии
|
|
|
Оу, в примере столько мороки, что преимущества совсем не видать. Но в целом интересно.
|
|
|
|
Тема хорошая, только применяется не там, где надо.
|
|
|
|
Денис, я, надеюсь, это будет во второй части
|
|
|
|
прицепить можно, а что конкретно с ним делать?
|
|
|
|
Этот пример понятен, но он также нарочито показывает лень разработчика.
|
|
|
|
Отлично! Интересно, спасибо :3
|
|
|
|
А чего раскрывать тему, тут в примере типа показываем, что нам лень отписываться (но не лень подписываться). Честное слово, пример с десериализацией чего-нибудь и тот интересней.
|
Последние записи от incvizitor
- Метатеги времени исполнения. (15.12.2012)













