Metoda getDefinitionByName i Reference Error: Error #1065

Ostatnio natknąłem się na problem z wykorzystaniem metody getDefinitionByName() służącej do tworzenia instancji klas w czasie wykonania na podstawie stringu definiującego pełną ścieżkę do klasy. Jest to jedna z kilku metod pozwalających programiście ActionScript na korzystanie z mechanizmu refleksji.

Pewnie większość z was zna ograniczenia jej wykorzystania, ale dla mnie z początku nie były one oczywiste, więc postanowiłem o tym napisać. Być może ktoś z was zna jakieś eleganckie rozwiązanie poniższego problemu.

Zależało mi na utworzeniu managera dla tworzenia różnych komponentów wchodzących w skład aplikacji. Na bazie dynamicznie dostarczanych informacji konfiguracyjnych manager tworzyłby instancje różnych klas w czasie wykonania aplikacji. Do tego typu zadań idealnie nadaje się metoda getDefinitionByName().

Z początku wszystko działało bez zarzutu (i to mnie zmyliło). Dla jednej klasy (net.satola.MyComponent) aplikacja kompilowała się i działała bez zarzutu. Pisząc testy postanowiłem zmienić nazwę klasy MyComponent tworzonej dynamicznie pozostawiając identyczną zawartość na MyComponent2. Wszystko powinno działać tak jak przedtem. Okazało się jednak, że w trakcie wykonania aplikacji Flash Player wyrzuca błąd: ReferenceError: Error #1065: Variable MyComponent2 is not defined. at global/flash.utils::getDefinitionByName()

Problematyczna okazała się następująca linia kodu:

var pwc:Class = Class(getDefinitionByName("net.satola.MyComponent2"));

Kompilator nie mógł odnaleźć klasy MyComponent2, mimo że podałem pełną ściężkę do pliku. Gdy parametrem metody getDefinitionByName() był ciąg znaków “net.satola.MyComponent” błędu nie było. Oba pliki (MyComponent.as i MyComponent2.as) były fizycznie obecne w folderze “net.satola”. O co chodzi? Zacząłem eksperymentować:

import net.satola.MyComponent2;

Nie pomogło. Dopisałem więc jeszcze:

private var _myComponent2:MyComponent2 = new MyComponent2();

I zadziałało. Ale to i tak bez sensu. Przecież nie o ty chodziło. O tym jakie klasy miały być wykorzystane manager miał być informowany w czasie wykonania aplikacji a nie w czasie jej kompilacji. Przy takim podejściu trzeba dodawać nowe instancje klas, które będą mogły (choćby teoretycznie) być wykorzystane przez managera i ponownie kompilować aplikację…

Problem leży w tym, że kompilator Flexa nie linkuje klas, do których nie ma odniesienia w kodzie. Jest to związane z optymalizacją wielkości aplikacji uruchamianej w Flash Playerze. Niby wszystko dobrze, ale takie podejście wyklucza sensowne użycie metody getDefinitionByName(). A może się mylę?

Za pierwszym razem dynamiczne utworzenie klasy MyComponent powiodło się tylko dlatego, że w innym miejscu aplikacji także korzystałem z tej klasy, więc jej definicja znalazła się w pliku swf.

Może ktoś z was zna eleganckie rozwiązanie powyższego problemu? Czy takie w ogóle istnieje?

This entry was posted in Ciekawe tematy, Flex 3. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

7 Comments

  1. Posted 19/05/2009 at 22:41 | Permalink

    Z tego co ja się spotkałem to podejście słuszne ale chyba wystarczy tylko tyle (żeby za bardzo nie śmiecić):

    private var _myComponent2:MyComponent = null;

    Podobny manewr jest wykorzystany w livedocs ( http://www.adobe.com/devnet/air/flex/articles/flex_air_codebase.html ) przy ustawianiu równoległej pracy na dwóch typach aplikacji air i Flex.

  2. Posted 19/05/2009 at 23:42 | Permalink

    Można też zmusić kompilator nakazujac explicite zaimportować dane klasy via argument mxmlc

    -includes class [...]

    albo wymusic zaimportowanie calego SWC z klasami
    via

    -include-libraries library [...]

    Tutaj masz dokumentacje:
    http://livedocs.adobe.com/flex/3/html/help.html?content=compilers_14.html

    Najwygodniej chyba zastosowac ANT’a do tego, szczegolnie w scenariuszu z wieloma klasami albo calym SWC. Mozna wtedy tak zmodyfikowac build by najpierw generowal sie SWC a potem wymusic jego import przy kompilacji głównego SWF’a.

  3. Posted 20/05/2009 at 01:41 | Permalink

    Wystarczy dac linijke:
    MyComponent2;
    np w konstruktorze klasy.
    Ogolnie zachowanie jak najbardziej porzadane, getDefinition najczesciej uzywa sie do uzyskiwania referencji do klas ( elementow ) ladowanych w zewnetrznym module, wiec tu jest pewnosc ( po zaladowaniu oczywiscie ), ze klasa bedzie dostepna. W samotnym swf’ie musimy ja sztucznie zainicjowac.

  4. Posted 20/05/2009 at 07:06 | Permalink

    Dzięki za komentarze, ale chodzi mi raczej o rozwiązanie problemu “sztucznego inicjowania” i rekompilacji swfa. Jeśli coś ma być dynamiczne, to nie może być wkompilowane. Hmm. może biblioteki ładowane dynamicznie? Też je trzeba przekompilować, ale nie jest to plik swf aplikacji… Chyba nie ma satysfakcjonującego rozwiązania.

  5. Posted 20/05/2009 at 07:24 | Permalink

    @jkozniewski: sorry, pisząc poprzedni komentarz nie widziałem Twojego wpisu. Twój sposób jest bliższy mojemu rozumowaniu, ale ciągle wymaga kompilacji gdy do zbioru klas będę chciał dodać nowe. Chyba lepiej byłoby ładować dynamicznie w czasie wykonania bibliotekę swc zawierającą definicje klas.

  6. Posted 20/05/2009 at 10:42 | Permalink

    Jeśli chcesz tworzyć klasy dynamicznie to faktycznie trzeba je ładować runtime’owo z zewnętrznego SWFa i wykorzystać odpowiedni ApplicationDomain żeby pobrać definicję klasy. Opis + przykład jest tutaj:

    http://help.adobe.com/en_US/AS3LCR/Flash_10.0/flash/system/ApplicationDomain.html

    W tym przykladzie do Loadera za pomoca ktorego ladujemy swfa z klasami, przekaywana jest referencja do biezacego ApplicationDomain. Czyli tak naprawde zamiast linii:

    return loader.contentLoaderInfo.applicationDomain.getDefinition(className) as Class;

    w metodzie ClassLoader#getClass mozna zrobic:

    return ApplicationDomain.currentDomain.getDefinition(className) as Class;

    albo po prostu:

    return getDefinitionByName(className) as Class;

  7. peper
    Posted 21/05/2009 at 08:09 | Permalink

    Niestety to nie Java. Tam pojedyncze klasy kompilują się do plików *.class i już ClassLoader może je w locie ładować. Jak tylko masz jakiś interfejs wspólny to wystarcza.
    Tutaj musisz pokompilować klasy do swf. Żeby mieć kontrolę nad tym co się dzieje można porobić RSL’e : http://livedocs.adobe.com/flex/3/html/help.html?content=rsl_01.html i tu http://labs.adobe.com/wiki/index.php/Flex_3:Feature_Introductions:Flex_3_RSLs

Post a Comment

You must be logged in to post a comment.