Статический метод для класса XMLLoader, как вернуть данные XML после функции Event.COMPLETE?

Я пытаюсь создать общий класс XMLLoader со статическим методом LOAD с намерением использовать его следующим образом...

private var _data:XML = XMLLoader.LOAD("path/to/xml_file.xml");

Тогда я мог бы использовать его в любом клиенте и отправиться в город с e4x.

Общая проблема, с которой я сталкиваюсь, связана с событием COMPLETE URLLoader, которое обязательно вызывает другую функцию для установки данных XML. Это не позволяет мне вернуть XML из метода LOAD, так как данные устанавливаются вне этой функции. Очевидно, мне нужно дождаться события COMPLETE, чтобы убедиться, что данные доступны.

Хотя, возможно, я мог бы создать и вернуть функцию _waitForData, которая рекурсивно вызывает себя до тех пор, пока не будут установлены _data, а затем возвращает данные. Но это кажется излишним (поскольку Event.COMPLETE все равно это делает), и то, как я пытался, генерирует ошибку переполнения стека.

Вот что я пробовал:

public class XMLLoader {

    private static var _url:String = "";
    private static var _data:XML = null;

    public static function LOAD(url:String):XML {

        _url = url;
        var _xmlLoader:URLLoader = new URLLoader();
            _xmlLoader.addEventListener(Event.COMPLETE, _setData);
            _xmlLoader.load(new URLRequest(_url));

        return _waitForData();
    }

    static function _setData(e:Event):void {
        XML.ignoreWhitespace = true;
        _data = new XML(e.target.data);
    }

    static function _waitForData():XML {

        if( _data == null ) {
             _waitForData();
        }

        return _data;
    }

    public function LoadXML() {}

}

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

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


person Logic Artist    schedule 18.07.2010    source источник


Ответы (2)


Вы не хотите идти по всем статическим маршрутам. Это беспорядок, усложняет вещи без необходимости, его трудно поддерживать, и что, если вы хотите загрузить два XML-файла одновременно? (подсказка: нельзя).

Вам лучше создать обычный класс, который делает то, что вы хотите (загрузить xml и отправить событие или вызвать функцию с результатами). Затем вы можете добавить более простой интерфейс для своего вызывающего кода; если это статическая функция, которая будет использоваться как однострочная, пусть будет так. Эту функцию можно рассматривать как своего рода фабрику, которая инициирует создание экземпляра класса, а также инициирует операцию загрузки, устанавливает обратный вызов результата, все за один шаг (с точки зрения вызывающей стороны).

Я думаю что-то в этом роде (только что написал это и отлично компилируется; не проверял его полностью, но должно быть достаточно, чтобы дать идею).

package
{
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.SecurityErrorEvent;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.utils.Dictionary;

    public class XMLLoader extends EventDispatcher
    {
        private var _loader:URLLoader;
        private var _data:XML;
        private var _callback:Function;

        private static var _map:Dictionary = new Dictionary();

        public function XMLLoader(callback:Function = null)
        {
            _data = null;
            _callback = callback;
            _loader = new URLLoader();
            _loader.addEventListener(IOErrorEvent.IO_ERROR,handleError);
            _loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,handleError);
            _loader.addEventListener(Event.COMPLETE,handleComplete);
        }

        public function load(request:URLRequest):void {
            _loader.load(request);  
        }

        private function dispatchError():void {
            cleanUp();  
            dispatchEvent(new Event(Event.COMPLETE));
            if(_callback is Function) {
                _callback(null);
            }
        }

        private function dispatchSuccess():void {
            cleanUp();  
            dispatchEvent(new Event(Event.COMPLETE));
            if(_callback is Function) {
                _callback(_data);
            }
        }

        private function handleError(e:Event):void {
            dispatchError();
        }

        private function handleComplete(e:Event):void {
            var success:Boolean = false;
            try {
                _data = new XML(e.target.data);
                success = true;
            } catch(err:TypeError) {
                success = false;
            } finally {
                if(success) {
                    dispatchSuccess();
                } else {
                    dispatchError();
                }
            }
        }

        public function get data():XML {
            return _data;
        }

        private function cleanUp():void {
            if(_loader) {
                _loader.removeEventListener(IOErrorEvent.IO_ERROR,handleError);
                _loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR,handleError);
                _loader.removeEventListener(Event.COMPLETE,handleComplete);
            }
            if(_map[this]) {
                delete _map[this];
            }
        }

        public static function loadXml(url:String,callback:Function):XMLLoader {
            var loader:XMLLoader = new XMLLoader(callback);
            loader.load(new URLRequest(url));
            //  pin down the instance just to be safe; I've seen loaders being collected...
            _map[loader] = true;
            return loader;
        }

    }
}

Использовать:

    private function handleResult(data:XML):void {
        if(data) {
            trace(data);
        } else {
            trace("failed");
        }
    }


    private function test():void {
        XMLLoader.loadXml("your.xml",handleResult);
    }

В качестве альтернативы вы можете переключиться на использование методов экземпляра, если вам нужна большая гибкость:

    private function test():void {
        var loader:XMLLoader = new XMLLoader(handleResult);
        loader.load(new URLRequest("your.xml"));        
    }

Этот пример реализации можно использовать с обратным вызовом или событиями. Для простоты я просто определяю одну функцию обратного вызова; вы можете сказать, успешно ли прошла операция загрузки, проверив полученные данные; ноль означает, что это не удалось. Кроме того, я отправляю событие Event.COMPLETE, чтобы сообщить, что загрузчик завершил загрузку (даже если она не удалась); опять же, вы можете узнать, не удалось это или удалось, проверив свойство данных. Вы можете использовать настраиваемые события, если хотите, и/или определить обратный вызов ошибки, но я думаю, что вам нужен максимально простой интерфейс, поэтому я думаю, что этого должно быть достаточно.

person Juan Pablo Califano    schedule 19.07.2010
comment
Это ЧРЕЗВЫЧАЙНО поучительный пример, намного превосходящий тот ответ, на который я надеялся. Я внимательно изучу ваши комментарии и примеры кода и предложу другим сделать то же самое. Спасибо! - person Logic Artist; 19.07.2010
comment
@Логический Художник. Нет проблем, я рад, что вы нашли мой ответ полезным. - person Juan Pablo Califano; 19.07.2010

Вы слишком многого требуете от системы, вспышка для этого не предназначена. Нельзя (и не нужно) вешать всю систему только для того, чтобы дождаться одного результата. Написание на flex/flash требует использования обработчиков завершения, потому что в некоторых случаях, таких как этот, просто невозможно обойти это. Если бы вы делали что-то таким образом, кадры никогда не перемещались бы вперед, и все в программе останавливалось бы до тех пор, пока вещь не возвращалась бы, создавая очень недовольный пользовательский опыт, который ощущался бы как «зависание» или «сбой».

С технической стороны, если бы вы абсолютно решительно настаивали на этом, то вам пришлось бы (1) найти способ добавить задержку в этот цикл if-null и (2) изменить его на while( _data != null ){ /* wait a while */ }, но я действительно не рекомендую идти по этому пути. В частности, вам нужно быть осторожным при вызове функции внутри себя. В вашем случае _waitForData просит _waitForData попросить _waitForData попросить _waitForData попросить... и т. д., пока не достигнет произвольного предела и не остановится. Это называется бесконечная рекурсия.

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

Обновлять:

Решение 2. Напишите класс, который обрабатывает всю необходимую загрузку XML. Вы можете предварительно заполнить данные в начале программы, если знаете, что может быть запрошено. Затем, когда вам понадобится больше, попросите класс добавить еще. Затем, если он уже загружен, он может просто вернуть его немедленно. Если его нет, он вернет null, извлечет его в фоновом режиме и вызовет обработчик, когда это будет сделано. Таким образом, код, вызывающий его, может, по крайней мере, немедленно попытаться сделать что-то с XML, но, в конце концов, ему всегда придется обрабатывать случай, когда он не загружен.

person eruciform    schedule 18.07.2010
comment
Справедливо... Я подумал, что рекурсивный цикл - плохая идея. Однако основная проблема заключается не в том, что цикл останавливает программу (это просто побочный эффект плохого решения)... мне нужен способ вернуть данные из статической функции, которая использует обработчик событий. Спасибо хоть. - person Logic Artist; 19.07.2010
comment
вернуть данные кому? должна ли функция быть статической? - person eruciform; 19.07.2010
comment
Возврат данных в контекст, в котором они вызываются. Идея состоит в том, чтобы передать URL-адрес методу (как показано выше) без создания экземпляра класса. Подумайте, скажем, о классе Math Flash, просто передайте значение одному из его статических методов, и он вернет ответ. Конечно, он не обязательно должен быть статичным, но таким образом я могу создать простой однострочный метод многократного использования. - person Logic Artist; 19.07.2010
comment
почему отвращение к созданию класса? вы будете делать это очень часто, например, много раз в секунду? если нет, я бы не беспокоился о накладных расходах. вы можете создать и уничтожить миллион XMLLoader за то время, которое требуется одному из них для возврата данных. :-) - person eruciform; 19.07.2010