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?
7 Comments
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.
Można też zmusić kompilator nakazujac explicite zaimportować dane klasy via argument mxmlc
albo wymusic zaimportowanie calego SWC z klasami
via
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.
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.
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.
@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.
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;
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