Фев 15 2011

Биндинг в ActionScript проектах. Часть 1

Category: ActionScript 3.0Diestro @ 20:49

Перед разработчиками часто встает вопрос о взамодействии различных классов в приложении. Как классы приложения будут обмениваться информацией между собой, сообщать о тех или иных изменениях? Это должно происходить своевременно. Для этих целей во Flex-фреймворке есть замечательный инструмент, значительно облегчающий жизнь разработчику – биндинг (binding). И как оказалось этой возможностью можно с успехом пользоваться и без использования Flex-фреймворка. Начиная с какой-то-там одной из третьих версий SDK адобовцы постарались максимально изолировать биндинг и все что с ним связано, и теперь его использование добавляет приложению считанные килобайты.

Как бы мы сделали

Немного отвлечемся и поговорим о частном случае. Когда же нам может все это дело пригодиться? Например, при разработке с использованием паттерна MVC встает вопрос о своевременном реагировании компонентов отображения на изменения в модели.

Как известно в MVC классы модели ничего не должны знать и не иметь ссылок ни на классы контроллера, ни на классы отображения. А вот на модель имеют ссылку и контроллер и отображение. Контроллеру нужна ссылка для того чтобы изменить модель, отображению – чтобы отреагировать на изменения. С изменением модели вроде все понятно – нужно поменять свойство. Но как отображение узнает о том что данные были изменены, чтобы самому измениться? Как правило для этих целей используются события. Мы создаем в модели сеттер на нужное нам свойство и в тот момент когда свойство меняется – посылаем соответствующее событие:

1
2
3
4
5
6
7
8
9
private var _text:String;

public function set text (value:String):void{
    if (value == _text){
        return;
    }
    _text = value;
    dispatchEvent(new Event("textChanged"));
}

Класс отображения имея ссылку на модель может поймать данное событие и отреагировать на него соответствующим образом. Все вроде хорошо, но есть ряд неудобств:

  1. Для каждого свойства в модели придется писать геттер/сеттер. А это много рутинной писанины, класс начинает пухнуть.
  2. При большом количестве свойств в модели будет либо расти количество типов событий, либо придется делать кастомное событие и передавать дополнительно что же именно изменилось и новое значение. В любом случае неудобно.

Как это делается с помощью биндинга

А как же все таки было бы здорово просто написать в классе отображения: “при изменении вот этого свойства вон в том экземпляре класса изменить вот это свойство” или вот так: “при изменении вот этого свойства вон в том экземпляре класса вызвать вот этот метод для обработки”. Так вот стандартный флэксовый биндинг работает именно так.

Давайте посмотрим как это будет выглядеть непосредственно в коде. То свойство за которым нам необходимо пристально следить нужно пометить как bindable. Делается это очень просто, с помощью метатега [Bindable]:

1
2
3
4
5
6
7
8
9
package{
    import flash.events.EventDispatcher;

    // Класс который использует метатег [Bindable] должен обязательно расширяться от EventDispatcher
    public class ClassWithTextProperty extends EventDispatcher{
        [Bindable]
        public var text:String;
    }
}

В классе который должен следить за свойством text в другом классе будет написано следующее:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package{
    import mx.binding.utils.BindingUtils;

    public class MyClass{
        //Экземпляр класса со свойством text, за которым нужно следить
        private var classWithTextProperty:ClassWithTextProperty = new ClassWithTextProperty();

        // Свойство которое будет изменяться при изменении свойства text в экземпляре classWithTextProperty
        private var myText:String;

        public function MyClass(){
            // Та самая строчка, которая позволяет связать свойство myText со свойством text
            BindingUtils.bindProperty(this, "myText", classWithTextProperty, "text");
        }
    }
}

В примере выше используя BindingUtils.bindProperty мы связываем свойство text со свойством myText, и теперь при изменении свойства text в экземпляре classWithTextProperty автоматически будет меняться и значение свойства myText. Здорово, правда?

Кратко расшифрую параметры принимаемые методом

bindProperty(site:Object, prop:String, host:Object, chain:Object, commitOnly:Boolean = false, useWeakReference:Boolean = false):ChangeWatcher

Параметры:

site: объект содержащий свойство для изменения;
prop: свойство для изменения;
host: объект, содержащий свойство за которым необходимо следить;
chain: в самом простом случае содержит строку с именем переменной, за которой нам необходимо следить. Возможны варианты указания цепочек в виде массива.

Есть еще свойство commitOnly на котором я подробно останавливаться в рамках данной статьи не буду и свойство useWeakReference – это то же самое что мягкие ссылки для событий, принцип тот же.

Возвращает данный метод экземпляр класса ChangeWatcher. Иногда сохранять возвращаемый экземпляр бывает очень полезно. Это нужно для того, чтобы мы смогли в любой момент “разбиндить” установленную связь и отписаться от изменений. Для этого у класса ChangeWatcher есть метод unwatch().

Второй вариант установления слежки за свойством text будет вызов нужного нам метода при его изменении.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package{
    import mx.binding.utils.BindingUtils;

    public class MyClass{
        // Свойство со ссылкой на  экземпляр класса со свойством text, за которым нужно следить
        private var classWithTextProperty:ClassWithTextProperty = new ClassWithTextProperty();

        public function MyClass(){
            // Та самая строчка, которая позволяет вызвать метод onTextChanged при каждом изменении свойства text
            BindingUtils.bindSetter(onTextChanged, classWithTextProperty, "text");
        }

        private function onTextChanged(value:String):void{
            trace("The new text value is: " + value);
        }
    }
}

В данном примере при каждом изменении свойства text в экземпляре classWithTextProperty, будет вызываться метод onTextChanged где в консоль будет выводиться её новое значение.

Кратко расшифрую параметры принимаемые методом

bindSetter(setter:Function, host:Object, chain:Object, commitOnly:Boolean = false, useWeakReference:Boolean = false):ChangeWatcher

Параметры

setter: метод, вызываемый при изменении указаного свойства или цепочки;
host: объект, содержащий свойство за которым необходимо следить;
chain: в самом простом случае содержит строку с именем переменной, за которой нам необходимо следить. Возможны варианты указания цепочек в виде массива.

Чаще всего используется именно метод bindSetter, он более гибкий и позволяет произвести нужную последовательность действий при изменении того или иного свойства.

Библиотека

А как же собственно использовать всю эту красоту в ваших ActionScript проектах? Есть вариант подключить все флэксовые библиотеки к вашему проекту в которых есть все необходимое для работы биндинга, но это очень скользкий путь, чреватый тем что в один прекрасный момент могут потянуться хвостом куча ненужных вам флэксовых классов, увеличивая размер конечной флэшки до немыслимых размеров. Гораздо надежнее собрать библиотеку из нужных для работы биндинга классов и использовать ее, не боясь что неожиданно потянутся хвосты, что собственно я и сделал. Размер получившейся библиотеки binding.swc получается порядка 15 Кбайт, а это не может не радовать. Таким образом использование биндинга незначительно влияет на объем конечной флэшки. Все что нужно сделать это подключить данную библиотеку к вашему проекту и “Вперед и с песней!”.

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

На самом деле ничего уникального в работе флэксового биндинга нет. Выше был описан класс ClassWithTextProperty. Так вот компилятор перед тем как откомпилировать проект преобразует этот класс к следующему виду:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package{
    import flash.events.EventDispatcher;
    import mx.events.PropertyChangeEvent;

    public class ClassWithTextProperty extends EventDispatcher{
        private var _text:String;

        public function set text(newValue:String) : void{
            var oldValue:* = this._text;
            if (oldValue !== newValue){
                this._text = newValue;
                this.dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "text", oldValue, newValue));
            }
        }

        public function get text() : String{
            return this._text;
        }
    }
}

Посмотрев на код видно что он делает тоже самое, о чем мы говорили вначале – посылает событие в результате изменения свойства text. Посредством BindingUtils мы по сути подписываемся на это событие и в результате либо меняется нужное нам свойство (bindProperty), либо вызывается нужый для обработки метод (bindSetter). Поэтому использование биндинга для разработчика это по сути упрощение рутинных действий по описанию и отлавливанию событий. Для того чтобы отловить изменение нужного нам свойства достаточно добавить две строки (метатег [Bindable] к свойству и BindingUtils.bind… в классе-обработчике) + добавить метод обработки в случае использования bindSetter.

Все должно быть в меру

При использовании биндинга, естественно, это нужно делать разумно и не вешать метатег [Bindable] на все свойства подряд. Важно помнить что в каждом таком случае при изменении этого свойства будет посылаться событие, а это лишняя нагрузка.

Из личного опыта бинднг в чистых ActionScript проектах используется очень часто и практически везде где это необходимо. При грамотном и осторожном его использовании нагрузка на процессор минимальна и незаметна.

Tags: ,

7 комментариев »

  1. комментарий by firsoff_max — 4 марта 2011 @ 11:09

    Большое спасибо, то что нужно!

  2. комментарий by Hyzhak — 29 июня 2011 @ 18:22

    Спасибо за информативную статью, а не подскажешь как увидеть “промежуточный” flex код?

  3. комментарий by Diestro — 29 июня 2011 @ 20:47

    Для того чтобы увидеть промежуточный код, нужно к параметрам компилятора добавить аргумент -keep-generated-actionscript=true
    В результате рядом со скомпилированной флэшкой будет лежать папка generated где собственно и будет весь хлам. Особенно интересно изучать содержимое данной папки для флэксовых проектов :)
    Если же промежуточного сгенеренного кода не будет, папка не появится (например для чистых actionscript проектов где биндинг и другие флэксовые библиотеки не используются).

  4. комментарий by Hyzhak — 30 июня 2011 @ 9:55

    У меня папка появляется но в случает Pure AS3 + мета-тэг [Bindable] – она пуста.

    Когда создал Flex проект + [Bindable], то файлы начали появляться в generated. Все mxml инструкции раскрыты в код, однако мета-теги как были? так оставались в коде не раскрытые. При чем swf добросовестно компилится и запускаются.

    Очевидно что-то не так, но что для меня пока остается загадкой.

    Использую Flash Builder Burrito + SDK Hero. Среди аргументов только: -keep-generated-actionscript=true.

    Спасибо за оперативность =)

  5. комментарий by Diestro — 1 июля 2011 @ 9:00

    Да, я ошибся. Если указывать -keep-generated-actionscript=true то папка generated будет появляться в любом случае. А вот ее содержимое зависит от того нужно ли что то генерить перед компиляцией. Если будет указан метатег для биндинга с указанием события (например [Bindable (event="propertyChange")]), то генериться ничего и не будет для него – все что нужно для компиляции есть.

    А метатеги не будут раскрываться в код всегда. Повторюсь – туда генерится промежуточный код, если для компиляции все есть и ничего “дописывать” не нужно там ничего и не появится.

  6. комментарий by Дмитрий — 11 сентября 2011 @ 0:08

    А в Abode Flash CS5 этот метод можно как нибудь применить?

  7. комментарий by Diestro — 11 сентября 2011 @ 12:25

    Да, для этого необходимо скачать вышеуказанную библиотеку о подключить ее через File -> ActionScript Settings, вкладка Library path.

RSS лента комментариев к этой записи. TrackBack URL

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

 

Comment Spam Protection by WP-SpamFree