Flex Lazy Binding - PullRequest
       17

Flex Lazy Binding

5 голосов
/ 30 марта 2012

Вдохновленный ленивыми способностями загрузки Hibernate, я хотел сделать модель частью моего Flex UI, запрашивая данные с сервера, только когда это необходимо. Я думал, что это будет так же просто, как добавление общедоступного метода доступа, который отправляет запросы к серверу только при обращении к переменной.

public function get tab2AC():ArrayCollection
{
    if(_tab2AC == null){
        //Request data from server
    }
    return _tab2AC;
}

Проблема в том, что Flex, кажется, обращается к всем связанным переменным при запуске приложения, даже если ссылочный компонент еще не создан. Таким образом, даже если DataGrid с dataProvider="{tab2AC}" еще не создан, запрос к серверу по-прежнему отправляется, что устраняет лень «только при необходимости».

Я не хочу помещать запрос на сервер в обработчик creationComplete, поскольку хочу, чтобы моя модель пользовательского интерфейса не учитывала состояние просмотра, а мое представление - о запросах к серверу.

Интересно, что если я добавлю Alert.show("anything"); внутри метода доступа, он будет работать как нужно.

ОБНОВЛЕНИЕ: вот полный пример. Установите точки останова, и вы увидите, что Flex обращается к обеим переменным, даже если titleForScreen2 не используется ни одним из созданных компонентов.

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
xmlns:s="library://ns.adobe.com/flex/spark" 
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
<fx:Script>
    <![CDATA[
        private var _titleForScreen1:String;
        private var _titleForScreen2:String;

        public function get titleForScreen1():String {
            if(_titleForScreen1 == null){
                //Server Request
            }                   
            return _titleForScreen1;
        }

        public function get titleForScreen2():String {
            if(_titleForScreen2 == null){
                //Server Request
            }
            return _titleForScreen2;
        }
    ]]>
</fx:Script>

<mx:ViewStack>
    <s:NavigatorContent label="Screen 1">
        <s:Label text="{titleForScreen1}"/>
    </s:NavigatorContent>
    <s:NavigatorContent label="Screen 2">
        <s:Label text="{titleForScreen2}"/>
    </s:NavigatorContent>
</mx:ViewStack>
</s:Application>

Ответы [ 6 ]

2 голосов
/ 31 марта 2012

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

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


Кстати, синтаксически я бы использовал обычный метод, а нечем собственность для возврата обещания.Свойства часто читаются как синхронные и дешевые (-ish) операции, тогда как метод (особенно тот, который возвращает обещание) будет иметь более гибкие коннотации.

1 голос
/ 01 апреля 2012
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
    <fx:Script>
        <![CDATA[
            import mx.controls.Alert;
            private var _titleForScreen1:String;
            private var _titleForScreen2:String;

            public function get titleForScreen1():String {
                if(_titleForScreen1 == null){
                    //Server Request
                }                   
                return _titleForScreen1;
            }

            public function get titleForScreen2():String {
                Alert.show("test");
                if(_titleForScreen2 == null){
                    //Server Request
                }
                return _titleForScreen2;
            }
        ]]>
    </fx:Script>

    <mx:ViewStack>
        <s:NavigatorContent label="Screen 1">
            <s:Label text="{titleForScreen1}"/>
        </s:NavigatorContent>
        <s:NavigatorContent label="Screen 2">
            <s:Label text="{titleForScreen2}"/>
        </s:NavigatorContent>
    </mx:ViewStack>
</s:WindowedApplication>

Точки останова в строках 12 и 19, проверяйте трассировку стека во время каждой из них, также вы можете открыть Binding и взглянуть на wrapFunctionCall (там также можно сбросить точку останова). Поэтому, когда он достигает строк 12 и 19, он попадает в них 2 раза из-за того, что предзагрузчик отправляет завершенное событие. Я поместил точки останова в каждый файл, в котором трассировка стека показала путь выполнения. К сожалению, я не смог найти точку, где это вызвало 2 вызова (должно быть в тех частях, для которых у меня нет источника), казалось, что каждая точка в трассе вызывается только один раз, но я думаю, что wrapFunctionCall вызывался дважды во время период этих двух казней. Третье, что происходит, происходит из-за вызова doPhasedInstantation, который вызывает выполнение для выполнения привязок для всех дочерних элементов, имеющих systemManager, поэтому может показаться, что компоненты каким-то образом имеют системного менеджера, даже если они еще не добавлены в этап или создан. Извините, у меня нет более конкретного ответа на вопрос, почему каждый из них должен произойти, но я думаю, что есть веская причина.

Ага, почти забыл, также вы увидите, что когда отображается предупреждение, оно вызывает ошибку, но эта ошибка фиксируется в методе wrappedFuncitonCall в Binding.as

catch(error:Error)
    {
        // Certain errors are normal when executing a srcFunc or destFunc,
        // so we swallow them:
        //   Error #1006: Call attempted on an object that is not a function.
        //   Error #1009: null has no properties.
        //   Error #1010: undefined has no properties.
        //   Error #1055: - has no properties.
        //   Error #1069: Property - not found on - and there is no default value
        // We allow any other errors to be thrown.
        if ((error.errorID != 1006) &&
            (error.errorID != 1009) &&
            (error.errorID != 1010) &&
            (error.errorID != 1055) &&
            (error.errorID != 1069))
        {
            throw error;
        }
        else
        {
            if (BindingManager.debugDestinationStrings[destString])
            {
                trace("Binding: destString = " + destString + ", error = " + error);
            }
        }
    }
0 голосов
/ 02 апреля 2012

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

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
xmlns:s="library://ns.adobe.com/flex/spark" 
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
<fx:Script>
    <![CDATA[
        import mx.binding.utils.BindingUtils;
        import mx.binding.utils.ChangeWatcher;

        private var _titleForScreen1:String;
        private var _titleForScreen2:String;

        public function get titleForScreen1():String {
            if(_titleForScreen1 == null){
                //Server Request
            }
            return _titleForScreen1;
        }

        public function get titleForScreen2():String {
            if(_titleForScreen2 == null){
                //Server Request
            }
            return _titleForScreen2;
        }

        public function updateLabel1(value:String):void {screen1Label.text = value;}
        public function updateLabel2(value:String):void {screen2Label.text = value;}

        public function bindLabel1():void {
            var changeWatcher:ChangeWatcher = BindingUtils.bindSetter(updateLabel1,this, "titleForScreen1");
        }

        public function bindLabel2():void {
            var changeWatcher:ChangeWatcher = BindingUtils.bindSetter(updateLabel2,this, "titleForScreen2");
        }
    ]]>
</fx:Script>

<mx:ViewStack>
    <s:NavigatorContent label="Screen 1">
        <s:Label id="screen1Label" creationComplete="bindLabel1()"/>
    </s:NavigatorContent>
    <s:NavigatorContent label="Screen 2">
        <s:Label id="screen2Label" creationComplete="bindLabel2()"/>
    </s:NavigatorContent>
    </s:NavigatorContent>
</mx:ViewStack>
</s:Application>
0 голосов
/ 31 марта 2012

Я думаю, что это просто шаткое поведение отладчика, а не то, что происходит в обычном исполнении.Если вы поместите туда любую логику, которая позволит вам определить, что была вызвана функция, которая не связана с отладчиком (например, установление текста на метке), тогда получатель не получитназывается.Однако, если вы поместите туда команду трассировки, getter действительно будет вызван.

Загадка состоит в том, насколько вы хотите зависеть от идеи, что это произойдет толькоотладка, и насколько важно получить реальное поведение при использовании проигрывателя отладки?

0 голосов
/ 30 марта 2012

Почему бы не вернуть обратно новую коллекцию ArrayCollection, если переменная равна нулю, а затем установить источник для коллекции ArrayCollection, когда вызов сервера вернется?

0 голосов
/ 30 марта 2012

Ваше утверждение не соответствует действительности, tab2AC При запуске приложение Flex не доступно приложению Flex, в качестве доказательства можно привести полный код приложения:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">

    <fx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;

            private var _tab2AC:ArrayCollection;

            public function set tab2AC(value:ArrayCollection):void
            {
                _tab2AC = value;
            }

            [Bindable]
            public function get tab2AC():ArrayCollection
            {
                if(_tab2AC == null){
                    trace("THIS WILL NOT BE CALLED");
                }
                return _tab2AC;
            }

        ]]>
    </fx:Script>

</s:Application>

Как видите, трассировка не будетбыть вызванным, так что ваша проблема, кажется, исходит от вызова этого геттера откуда-то в вашем приложении, чтобы найти его, поставить точку останова и затем "Шаг возврата", когда это необходимо.

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

...