직장을 옮기면서 Flash에 관심을 가진지도 3년이 넘었다. 당시만해도 PHP정도만 경험이 있었고 수년동안 웹과는 단절된 상태였기 때문에 Ajax로 만들어진 구글맵을 보고 "이런걸 만들 수 있구나..."라는 말을 중얼거리며 꽤 놀랐던 적이 있었다. Web 2.0, RIA라는 단어가 당시 트렌드였고 이 모든 것이 나에게는 매우 신선했고 가능성을 느끼게 하는데 충분했다. 

하지만 3년전 RIA 대표주자인 Flex 2를 처음 접했을때 국내 관련서적은 거의 없었다. Flash IDE를 기반으로 하는 책은 수도 없이 많았지만 개발요구사항을 만족시키기에는 매우 역부족이였고 Flex도 옥상훈님이 쓴 서적 딱 한권 뿐이였다. 사실 모두 기초서적들이라 초반학습에는 도움되었지만 실무에 적용하기에는 너무 부족했다. 또한 기술적으로 도움을 받을 수 있는 인맥도 전혀 없었다.

결국 내가 선택한 방법은 이 블로그이다. 말이 되던 안되던 Flex 하나를 중구장창 파고 들면서 얻은 귀중한 지식을 블로그에 하나씩 써내려갔다. 초반에는 livedocs가 어디 있는지도 몰랐고 구글 검색도 처음 해봤던 시절이라 너무 힘들었다. 블로그의 내용은 관련 카페에 소개하면서 계속 점접을 만들어갔다. 이러한 노력은 몇개월이 지나지 않아 바로 사람들에게 좋은 반응으로 이어졌고 지식공유가 일어났다. 또한 이쯤에 flexdocs.kr 사이트도 운영하게 되었다. 그때 생각하면 너무 어렵게 공부했던 기억뿐이다.

3년이 지난 지금... 사정은 너무도 많이 달라졌다. 3년전 한창 붐을 일으키던 RIA라는 단어는 이미 일상이 되버렸고 모바일이 이슈가 되면서 관련 서적도 쏟아지기 시작했다. Flash Platform에 관련된 서적도 엄청 많이 쏟아졌고 오프라인 서점만 찾아가면 듣도보지도 못한 관련서적을 꽤 많이 발견할 수 있었다.

아무튼 이젠 Flash 공부하는데 천국이 된 것임에 틀림없다.

나는 최근에 국내에 출판된 좋은 서적 4권을 소개하고자 한다.


okgosu의 액션스크립트 정석



okgosu의 액션스크립트 정석은 Flex 서적 2편으로 유명하신 옥상훈님의 책이다. 이 책은 ActionScript 3.0의 전반적인 내용을 다루고 있다. 기초문법부터 시작해 개발환경 및 디스플에이 객체를 다루는 방법을 꽤 상세하게 다루고 있다. 또한 Flash Player 10에 추가된 3D API의 기초를 다루고 있기 때문에 그동안 3D에 대해서 잘 몰랐던 사람이 공부하기에 딱 좋은 서적인 것 같다. 책의 내용이 방대하고 수많은 예시가 있어 ActionScript 3.0을 처음하는 사람에게 꽤 도움이 될 것이다.


플래시 ON 디바이스



이철규님이 번역하신 플래시 ON 디바이스는 Flash Platform을 이용해 모바일 애플리케이션을 개발하고자 하는 사람들을 위한 책이다. 사실 모바일 분야는 Flash Lite에 한정되어 있어서 필자처럼 Flex/ActionScript 3.0 기반으로만 했던 사람들에게는 넘어가기 힘든 장벽이였다. 하지만 이책은 Flash 모바일의 역사를 자세하게 소개하고 꽤 고급주제 심도있게 다루고 있어서 Flash를 이용한 모바일 개발에 관심을 가지는 사람에게 추천해줄 수 있는 책이다.  


비주얼 플렉스 UX 디자인

열이아빠님으로 활동중인 이준하님이 번역하신 비주얼 플렉스 UX 디자인은 Flex 3의 시각 컴포넌트를 사용하면서 생길 수 있는 다양한 어려움을 극복하는데 큰 도움을 준다. 레이아웃, 효과, 스타일, 스킨등을 전반적으로 다루면서 좀더 심도있게 컴포넌트를 적용할 수 있는 방법을 잘 설명해주고 있다. Flex를 이용해 풍부한 UX를 사용자들에게 경험시키고자 할때 이책은 큰 도움이 될 것이다.


ActionScript 3.0 Animation


윤도선님이 번역하신 시작하세요! 액션스크립트 3.0 애니메이션은 내가 베타 리더로도 참여했던 책이다. Flash IDE 타임라인을 이용해 애니메이션을 적용하던 수준을 넘어서 코드 기반으로 수학,물리를 이용한 다양한 에니메이션을 주는 기법에 대해서 아주 쉽게 설명하고 있다. 삼각함수, 속도, 가속도, 마찰, 바운드, 모션공식 일반적으로 수학/물리에 어려움을 느끼는 개발자들에게 꽤 유용하다. 또한 3D API를 이용하는 예도 담겨 있어 더욱 다양한 경험을 준다.


정리하며
최근에 발간된 총 4권의 책을 소개했다. 앞서 설명했듯이 3년전에 이런 고귀한 책들이 있었다면 아마도 지금까지 공부했던 시간의 절반이상은 절약할 수 있었을 것이다. 아무튼 좋은 책은 돈에 구애받지 말고 구입해서 자신의 것으로 만들어 가려는 노력이 빠른 배움의 지름길이다.

글쓴이 : 지돌스타(http://blog.jidolstar.com/690)

LCDS, BlazeDS, ZendAMF등을 이용해 AMF(ActionScript Message Format)으로 데이터를 주고받는 형태는 이미 많은 예제들과 문서들이 있다. AMF는 데이터 통신을 위한 일종의 약속된 규약이고 이 데이터를 주고 받고 하는 과정에서 서로의 언어에 맞게 직렬화(Serialization)하는 것은 각 언어에서 지원해주는 라이브러리를 사용하면 된다. ActionScript 3.0은 기본 API에서 지원해준다. 자바의 경우는 BlazeDS나 LCDS를 사용하면 된다. PHP의 경우에는 ZendAMF를 사용하면 된다. 이들을 이용하면 가령, 자바와 java.lang.Interger는 ActionScript의 int나 uint로... java.lang.Double은 ActionScript의 Number형과 직렬화된다. 이는 일종의 기본적으로 지원되는 직렬화이다.

다음은 BlazeDS에서 기본적으로 지원되는 직렬화이다.
Serializing between ActionScript and Java

하지만 이런 기본 직렬화과정을 사용하지 않고 직렬화 자체를 커스터마이징(customizing)할 수 있다. 가령 클라이언트(예,Flash 애플리케이션)에서는 아이디, 이름, 속성, 가격의 정보가 중요하지만 서버(예,자바)에서는 재고(inventory)가 중요한 경우가 있다. 이런 경우에는 Flex와 Java쪽의 직렬화하는 클래스를 다음과 같이 디자인 할 수 있겠다.

// Product.as
package samples.externalizable {

import flash.utils.IExternalizable;
import flash.utils.IDataInput;
import flash.utils.IDataOutput;

[RemoteClass(alias="samples.externalizable.Product")]
public class Product implements IExternalizable {
    public function Product(name:String=null) {
        this.name = name;
    }

    public var id:int;
    public var name:String;
    public var properties:Object;
    public var price:Number;

    public function readExternal(input:IDataInput):void {
        name = input.readObject() as String;
        properties = input.readObject();
        price = input.readFloat();
    }

    public function writeExternal(output:IDataOutput):void {
        output.writeObject(name);
        output.writeObject(properties);
        output.writeFloat(price);
    }
}
}


// Product.java
package samples.externalizable;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Map;

/**
* This Externalizable class requires that clients sending and 
* receiving instances of this type adhere to the data format
* required for serialization.
*/
public class Product implements Externalizable {
    private String inventoryId;
    public String name;
    public Map properties;
    public float price;

    public Product()
    {
    }

        /**
        * Local identity used to track third party inventory. This property is
        * not sent to the client because it is server-specific.
        * The identity must start with an 'X'.
        */
        public String getInventoryId() {
            return inventoryId;
        }

        public void setInventoryId(String inventoryId) {
            if (inventoryId != null && inventoryId.startsWith("X"))
            {
                this.inventoryId = inventoryId;
            }
            else
            {
                throw new IllegalArgumentException("3rd party product
                inventory identities must start with 'X'");
            }
        }

        /**
         * Deserializes the client state of an instance of ThirdPartyProxy
         * by reading in String for the name, a Map of properties
         * for the description, and 
         * a floating point integer (single precision) for the price. 
         */
        public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
            // Read in the server properties from the client representation.
            name = (String)in.readObject();
            properties = (Map)in.readObject();
            price = in.readFloat();
            setInventoryId(lookupInventoryId(name, price));
        }
        /**
         * Serializes the server state of an instance of ThirdPartyProxy
         * by sending a String for the name, a Map of properties
         * String for the description, and a floating point
         * integer (single precision) for the price. Notice that the inventory 
         * identifier is not sent to external clients.
         */
        public void writeExternal(ObjectOutput out) throws IOException {
            // Write out the client properties from the server representation
            out.writeObject(name);
            out.writeObject(properties);
            out.writeFloat(price);
        }
        
        private static String lookupInventoryId(String name, float price) {
            String inventoryId = "X" + name + Math.rint(price);
            return inventoryId;
        }
}

위 코드는 ActionScript의 flash.utils.IExternalizable 인터페이스와 Java의 java.io.Externalizable 인터페이스를 이용해 기본직렬화를 무시하고 이들 인터페이스에 정의된 readExternal와 writeExternal 메소드를 호출하여 직렬화 자체를 커스터마이징 할 수 있다는 것을 의미한다. Externalizable 인터페이스를 구현한 클래스에 정의된 메소드가 기본 직렬화보다 우선순위가 높다는 것을 기억하면 되겠다.

Flex의 ArrayCollection을 다시 한번 보기 바란다. 이 클래스는 flash.utils.IExternalizable를 구현했다.
mx.collections.ArrayCollection

꼭 이런 경우만은 아니다. 쓸데없는 데이터의 크기를 줄이기 위한 방법도 해당한다. 예를들어 ActionScript 코드를 보면 다음과 같다.


class Example implements IExternalizable {
  
      public var one:Boolean;
      public var two:Boolean;
      public var three:Boolean;
      public var four:Boolean;
      public var five:Boolean;
      public var six:Boolean;
      public var seven:Boolean;
      public var eight:Boolean;
       public function writeExternal(output:IDataOutput) {
           var flag:int = 0;
           if (one) flag |= 1;
          if (two) flag |= 2;
          if (three) flag |= 4;
          if (four) flag |= 8;
          if (five) flag |= 16;
          if (six) flag |= 32;
          if (seven) flag |= 64;
          if (eight) flag |= 128;
           output.writeByte(flag);
      }
       public function readExternal(input:IDataInput) {
           var flag:int = input.readByte();
           one = (flag & 1) != 0;
          two = (flag & 2) != 0;
          three = (flag & 4) != 0;
          four = (flag & 8) != 0;
          five = (flag & 16) != 0;
          six = (flag & 32) != 0;
          seven = (flag & 64) != 0;
          eight = (flag & 128) != 0;
      }
 }

데이터 통신을 위해 쓸데없이 Boolean객체를 주고 받을 필요없다. 위 코드처럼 직렬화를 커스터마이징한다면 송수신 데이터 자체의 크기도 줄일 수 있다. 이는 매우 유용하다.


참고글
flash.utils.IExternalizable
커스텀 직렬화의 사용
ActionScript 3.0 데이터 유형 및 직렬화(serialization)
Serializing between ActionScript and Java
AMF 3 스팩
AS3 BitmapData AMF solution using IExternalizable
Flex and PHP: remoting with Zend AMF

글쓴이 : 지돌스타(http://blog.jidolstar.com/644)


Flash 애플리케이션을 만들때  통합개발환경(IDE)인 Flash CS3, CS4 나 Flex Builder, Flash Builder, FDT와 같은 툴을 주로 사용할 것이다. 실무에서는 당연히 이런 툴을 선택할 수 밖에 없다. 그러나 이들은 사실 유료이고 무겁다.

Flash MiniBuilder라는 프로그램은 간단하게 ActionScript 3.0 기반으로 개발할 수 있도록 Adobe AIR로 만들어진 가벼운 개발툴이다. 이것은 Victor Drâmbă라는 사람이 만들었으며 그의 블로그(http://www.victordramba.com)를 가보면 MiniBuilder에 대한 소식을 볼 수 있다.

MiniBuilder는 Flex SDK 3와 Java JRE 설치를 먼저 해야 정상적으로 사용할 수 있으며 사용하는 방법은 다음 동영상에서 잘 나와있다. 약간 복잡해 보이지만 중간에 start.bat를 실행해야하는 번거로움은 AIR 2.0에서는 없어질 것이라 생각한다.
http://www.youtube.com/watch?v=bjEc2eT_rCE&feature=player_embedded


이 프로그램은 Flex SDK에서 제공하는 컴파일러를 이용해서 만들어진 ActionScript 3.0코드를 컴파일하게 된다. 워낙 가볍기 때문에 Flash Builder에서 느껴지는 묵직함은 찾아볼 수 없다. 일반적인 코딩도 별 어려움이 없고 또한 코드힌트기능 및 에러메시지 출력 기능도 있기 때문에 이 또한 유용하다. 아쉬운 점은 디버깅을 할 수 없고 한글 주석도 못단다는 점.

MiniBuilder는 공개소스이다. GPL 라이센스를 가지며 Google Code(http://code.google.com/p/minibuilder/)에서 전반적인 설명과 소스코드를 볼 수 있다. 원한다면 Flex Builder 3나 Flash Builder 4에서 SVN으로 다운로드 받아 직접 소스를 테스트해볼 수도 있다. (본인도 SVN으로 다운받아 해봤다. 자바소스까지 포함되어 있으므로 Eclipse+Flex Builder 3 Plug-in 환경에서 작업할 것을 권한다. MXML 사용 안하고 전부 ActionScript 3.0으로 만들었다.)



MiniBuilder는 AIR뿐 아니라 Flash버전으로도 제작되어 유명한 ActionScript 3.0 코드 공유사이트인 Wonderfl의 에디터로도 활용되었다. 다음 제작자의 글을 보면 그에 대한 소개가 나와있다.
http://www.victordramba.com/?p=71



벌써 이것이 만들어져 소개된지 몇개월 되었는데 이렇게 멋진 공개 프로그램이 한국에 소개된게 딱 하나밖에 못본 것 같아 간단하게 소개해본다.

참고 링크
Victor Drâmbă의 블로그 : http://www.victordramba.com
Flash MiniBuilder Google Code : http://code.google.com/p/minibuilder
설치 및 사용방법 : http://www.youtube.com/watch?v=bjEc2eT_rCE&feature=player_embedded


글쓴이 : 지돌스타(http://blog.jidolstar.com/642)
Adobe AIR는 다양한 OS(Windows, Mac, Linux)에 구동될 수 있는 애플리케이션을 만드는데 사용하는 일종의 Flash Platfom기술이다. Flash ActionScript 3.0이나 Ajax만으로도 AIR 애플리케이션을 만들 수 있기 때문에 기존에 웹개발자들이 다른 언어를 배우지 않고 일반 데스크탑용 애플리케이션을 만드는데 있어서 접근성이 좋다. 앞으로 AIR는 데스크탑뿐 아니라 모바일등과 같은 다양한 기기에도 적용될 예정이다.

하지만 AIR외에 다른 데스크탑용 애플리케이션과 비교해 그 적용범위가 아직까지 많은 부분 부족하고 복잡한 UI를 다루는데 있어서 성능문제가 걸릴 수 있다. 하지만 AIR 2.0 Beta 버전이 오픈된 것을 볼 수 있었듯이 계속 발전해나갈 것이다. 물론 성능면에서도 마찬가지 일 것이다. 그래도 약간은 부족하다. 기능은 그렇다 쳐도 지금의 성능을  개발자가 조금만 신경을 쓴다면  획기적으로 향상시킬 수 있다.

일전에 Reducing CPU usage in Adobe AIR라는 글을 보았다. 여기서는 매우 단순하고 쉬운 방법으로 framerate를 줄임으로서 CPU 사용율을 현격히 줄이는 방법을 소개하고 있다.

이 방법이 퍼포먼스를 올리기 위한 방법의 하나의 예시는 될 수 있지만 그 전부는 아니라는 점은 밝혀둔다.

(이 글을 완벽히 이해하기 위해서는 ActionScript 3.0과 AIR에 대한 전반적 이해가 요구됩니다.)

FrameRate를 줄임
Framerate를 줄임(Framerate throttling)은 애플리케이션의 휴면(idle)이 있을때 자원 사용을 줄여 퍼포먼스를 증가시키는 기술을 의미한다. 이를 구현하기 위해 ActionScript 3.0에서 매우 유용한 속성으로 Stage.frameRate가 있다. 이것을 이용하면 런타임시에 애플리케이션의 framerate를 변경시킬 수 있다. 

여기서 보여지는 예제는 Reducing CPU usage in Adobe AIR에서 소개한 글에 나온 예제를 조금더 실용적으로 만들었다.(실제로 실행해 볼 수 있도록) Flash Builder 4와 AIR 2.0 SDK를 설치한 사람들이라면 이 예제를 실제로 테스트 해볼 수 있다. 아래 순서대로 개발/테스트 환경을 구축하면 된다.

  • 만약 Flash Builder를 설치 안했다면 다음 링크를 통해 받으세요.
  • SDK와 Runtime을 다운로드 받습니다.
  • 다운받은 Runtime을 실행해 설치합니다.
  • (MS Windows의 경우)SDK는 압축을 풀고 그안에 있는 내용을 Flash builder가 설치된 sdks/4.0.0과 sdks/3.4.1 폴더에 각각 덮어씌웁니다. 제 경우는 C:\Program Files\Adobe\Adobe Flash Builder Plug-in Beta 2\sdks\4.0.0
  • Flash Builder를 실행합니다.
  • 메뉴에서 File > New > Flex Project를 선택합니다.
  • 프로젝트 이름을 적고 Application Type은 AIR를 선택합니다.
  • Next버튼을 두번 클릭후 Main Application file이름이 프로젝트명.mxml로 되어 있다면 Novice.as로 바꾸세요. 그리고 Finish 버튼을 누릅니다.
  • 아래 첫번째 초급 코드를 복사해서 Novice클래스를 열어 붙힙니다.
  • 디버그 모드로 테스트합니다.
  • 두번째, 세번째의 경우 만들어진 프로젝트의 소스폴더에 해당 클래스 이름으로 Class를 만듭니다. File > New > ActionScript Class를 선택한뒤 아래 소스를 복사해 붙여넣으면 되겠죠?
  • Package Explorer에 프로젝트명을 선택후 마우스 우클릭해서 컨텍스트 메뉴에서 Properties를 선택합니다.
  • 창이 뜨면 좌측 Flex Applications를 선택하고 오른쪽에 Add를 눌러 추가한 클래스(Intermediate.as, Expert.as)를 추가한뒤 OK를 합니다. Intermediate-app.xml과 Expert-app.xml이 자동 생성됩니다.
  • 이제 디버그 모드로 실행하면서 동작하는 형태를 학습하세요.
  • 아래 소스는 다음 링크에서 다운로드 받으셔도 됩니다.



  • 초급
    Framerate를 줄일때의 시점을 선택하는데 가장 쉽고 유용한 방법은 NativeApplication에서 Event.ACTIVATE 와 Event.DEACTIVATE 이벤트를 사용하는 것이다. AIR로 만들어진 빈윈도우를 선택해서 사용할때 CPU사용율이 1.8%라면 다른 윈도우를 선택해 그 윈도우의 CPU 사용율이 0.4%까지 떨여졌다고 한다. 또한 framerate를 0.01로 지정하면 0.2%까지 떨어진다고 한다. 이에 대한 예제는 다음과 같다. 예제가 이해하기 쉬우므로 따로 설명하지 않겠다.


    package {
    	import flash.desktop.NativeApplication;
    	import flash.display.NativeWindow;
    	import flash.display.NativeWindowInitOptions;
    	import flash.display.NativeWindowType;
    	import flash.display.Sprite;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	import flash.events.Event;
    	import flash.text.TextField;
    	import flash.text.TextFieldAutoSize;
    	import flash.utils.getTimer;
    	
    	/**
    	 * CPU 사용 줄이기 예제 1 
    	 * @author Yongho, Ji
    	 * @since 2009.12.1
    	 * @see http://blog.jidolstar.com/622
    	 */ 
    	public class Novice extends Sprite {
    		private var __isActive:Boolean = false;
    		private var __window:NativeWindow;
    		private var __textField:TextField;
    		public function Novice() {
    			__init();
    			var options:NativeWindowInitOptions = new NativeWindowInitOptions();
    			options.type = NativeWindowType.UTILITY;
    			
    			__window = new NativeWindow(options);
    			__window.width = 200;
    			__window.height = 200;
    			__window.title = "Novice";
    			
    			__textField = new TextField();
    			__textField.autoSize = TextFieldAutoSize.LEFT;
    			__textField.text = "";
    			
    			__window.stage.scaleMode = StageScaleMode.NO_SCALE;
    			__window.stage.align = StageAlign.TOP_LEFT;
    			
    			__window.stage.addChild(__textField);
    			__window.activate();
    			__window.addEventListener(Event.CLOSING,__onClosing);
    		}
    		private function __init():void {
    			NativeApplication.nativeApplication.addEventListener(Event.ACTIVATE, __onActive );
    			NativeApplication.nativeApplication.addEventListener(Event.DEACTIVATE, __onDeactive );
    			stage.addEventListener(Event.ENTER_FRAME,__onEnterFrame);
    		}
    		private function __onActive($event:Event):void {
    			stage.frameRate = 50;
    			__isActive = true;
    		}
    		private function __onDeactive($event:Event):void {
    			stage.frameRate = 1;
    			__isActive = false;
    		}
    		private function __onEnterFrame($event:Event):void {
    			__textField.text =  "active:" + __isActive + " " + getTimer();
    		}
    		private function __onClosing($event:Event):void {
    			NativeApplication.nativeApplication.exit();
    		}
    	}
    }
    


    중급

    위의 예제보다 조금더 고급적으로 framerate를 조절할 필요가 있다. 가령, 마우스 휠 이벤트에 따라 스크롤이 되는 컨텐츠가 있는 경우가 그것인데 평소에는 작은 framerate를 유지하다가 스크롤시에 빠른 렌더링이 필요하므로 framerate를 올려주는 것이다. 구체적으로 MouseEvent.MOUSE_WHEEL이 발생시 framerate를 올려주고 Event.ENTER_FRAME 이벤트에서 스크롤링후 500ms이 지난 다음 다시 framerate를 줄이는 것이다. 이도 휴면(idle)상태에서 쓸데없이 렌더링되는 것을 방지하고 필요한 동작할 때만 빠른 렌더링을 요구하도록 함으로써 CPU 사용율을 줄여주는 것이다.

    package {
    	import flash.desktop.NativeApplication;
    	import flash.display.NativeWindow;
    	import flash.display.NativeWindowInitOptions;
    	import flash.display.NativeWindowType;
    	import flash.display.Sprite;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	import flash.events.Event;
    	import flash.events.MouseEvent;
    	import flash.text.TextField;
    	import flash.text.TextFieldAutoSize;
    	import flash.utils.getTimer;
    	/**
    	 * CPU 사용 줄이기 예제 2 
    	 * @author Yongho, Ji
    	 * @since 2009.12.1
    	 * @see http://blog.jidolstar.com/622
    	 */ 
    	public class Intermediate extends Sprite {
    		private static const ACTIVE:int = 50;
    		private static const INACTIVE:int = 1;
    		private var __isActive:Boolean = false;
    		private var __isScrolling:Boolean = false;
    		private var __buffer:int;
    
    		private var __window:NativeWindow;
    		public function Intermediate() {
    			__init();
    			var options:NativeWindowInitOptions = new NativeWindowInitOptions();
    			options.type = NativeWindowType.UTILITY;
    			
    			__window = new NativeWindow(options);
    			__window.width = 200;
    			__window.height = 200;
    			__window.title = "Intermediate";
    			
    			var textField:TextField = new TextField();
    			textField.y = 20;
    			textField.width = 195;
    			textField.height = 175;
    			textField.multiline = true;
    			textField.wordWrap = true;
    			textField.border = true;
    			textField.borderColor = 0xff0000;
    			textField.text = "Adobe AIR는 다양한 OS(Windows, Mac, Linux)에 구동될 수 있는 애플리케이션을 만드는데 사용하는 일종의 Flash Platfom기술이다. Flash ActionScript 3.0이나 Ajax만으로도 AIR 애플리케이션을 만들 수 있기 때문에 기존에 웹개발자들이 다른 언어를 배우지 않고 일반 데스크탑용 애플리케이션을 만드는데 있어서 접근성이 좋다. 앞으로 AIR는 데스크탑뿐 아니라 모바일등과 같은 다양한 기기에도 적용될 예정이다.하지만 원천적으로 데스크탑용 애플리케이션과 비교해 그 적용범위가 아직까지 많은 부분 부족하고 복잡한 UI를 다루는데 있어서 성능문제가 걸릴 수 있다. 적용되는 범위는 AIR 2.0 Beta 버전이 오픈된 것을 볼 수 있었듯이 계속 발전해나갈 것이다. 또한 성능면에서도 마찬가지 일 것이다. 그래도 성능면에 있어서 개발자가 조금만 신경을 쓴다면 어떤 부분에 있어서 획기적으로 AIR 애플리케이션의 성능을 향상시킬 수 있다. 일전에 Reducing CPU usage in Adobe AIR라는 글을 보았다. 여기서는 매우 단순하고 쉬운 방법으로 framerate를 줄임으로서 CPU 사용율을 현격히 줄이는 방법을 소개하고 있다. ";
    
    			__window.stage.scaleMode = StageScaleMode.NO_SCALE;
    			__window.stage.align = StageAlign.TOP_LEFT;
    			
    			__window.stage.addChild(textField);
    			__window.activate();
    			__window.addEventListener(Event.CLOSING,__onClosing);
    		}
    		private function __init():void {
    			NativeApplication.nativeApplication.addEventListener(Event.ACTIVATE, __onActive );
    			NativeApplication.nativeApplication.addEventListener(Event.DEACTIVATE, __onDeactive );
    			stage.addEventListener(MouseEvent.MOUSE_WHEEL, __onMouseWheel, true);
    		}
    		private function __onActive($event:Event):void {
    			stage.frameRate = ACTIVE;
    			__isActive = true;
    			trace( "active" );
    		}
    		private function __onDeactive($event:Event):void {
    			stage.frameRate = INACTIVE;
    			__isActive = false;
    			trace( "deactive" );
    		}
    		private function __onMouseWheel($event:MouseEvent):void {
    			if( !__isActive ) {
    				if ( !__isScrolling ) {
    					stage.addEventListener(Event.ENTER_FRAME, __onEnterFrame, true);
    				}
    				stage.frameRate = ACTIVE;
    				__isScrolling = true;
    				__buffer = getTimer()+500;
    			}
    		}
    		private function __onEnterFrame($event:Event):void {
    			if( __buffer < getTimer() ) {
    				stage.frameRate = INACTIVE;
    				__isScrolling = false;
    			}
    		}
    		private function __onClosing($event:Event):void {
    			NativeApplication.nativeApplication.exit();
    		}
    	}
    }
    


    고급

    위에서 소개한 경우보다 약간더 어려운 주제로 넘어가보자. 먼저 창이 보이지 않을때와 보일때에 어떻게 처리할 것인가이다. 보이는 경우라면 일단 최소한의 framerate를 5로 주고 안보인다면 1로 준다. 이 경우는 MS Windows에서는 Tray Icon으로 바뀌며 창이 안보여질 때나 Mac에서 Dock으로만 표시될 필요가 있을때 사용될 수 있을 것이다. 굳이 보여지지 않는데 지나친 framerate를 줄 필요가 없기 때문이다. 평상시 Active한 상태에서는 24 framerate를 유지하지면 때에 따라서 부드럽게 운동하는 모습을 렌더링할 필요가 있을 때가 있다. 상태변화에 따라 Tweener 기능을 사용하는 경우가 그것인데 이때는 평소보다 framerate를 올려줄 필요가 있을 수 있다.

    아래 AIR 애플리케이션 소스코드는 두개의 버튼이 있다. 한개는 창의 visible을 false로 지정했다가 1초후 다시 true로 해주는 것이고 또 하나는 가상의 애니메이션이 있다고 가정하고 1초정도 여분을 둔다. 각 상태가 변할때마다 framerate를 Dubugging 시에 콘솔창에서 상태변화를 확인할 수 있도록 짜여있다.

    package {
    	import flash.desktop.NativeApplication;
    	import flash.display.NativeWindow;
    	import flash.display.NativeWindowInitOptions;
    	import flash.display.NativeWindowType;
    	import flash.display.Shape;
    	import flash.display.SimpleButton;
    	import flash.display.Sprite;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	import flash.events.Event;
    	import flash.events.MouseEvent;
    	import flash.filters.BevelFilter;
    	import flash.text.TextField;
    	import flash.text.TextFormat;
    	import flash.utils.getTimer;
    	import flash.utils.setTimeout;
    	/**
    	 * CPU 사용 줄이기 예제 3 
    	 * @author Yongho, Ji
    	 * @since 2009.12.1
    	 * @see http://blog.jidolstar.com/622
    	 */ 
    	public class Expert extends Sprite {
    		public static const ANIMATING:int = 50;
    		public static const ACTIVE:int = 24;
    		public static const INACTIVE_VISIBLE:int = 5;
    		public static const INACTIVE_INVISIBLE:int = 1;
    		
    		private var __isActive:Boolean = false;
    		private var __isAnimating:Boolean = false;
    		private var __window:NativeWindow;
    		private var __buffer:int;
    		
    		public function Expert() {
    			__init();
    			var options:NativeWindowInitOptions = new NativeWindowInitOptions();
    			options.type = NativeWindowType.UTILITY;
    			
    			__window = new NativeWindow(options);
    			__window.width = 200;
    			__window.height = 200;
    			__window.title = "Expert";
    			
    			__window.stage.scaleMode = StageScaleMode.NO_SCALE;
    			__window.stage.align = StageAlign.TOP_LEFT;
    			
    			//창을 감추기 버튼 
    			var buttonSkin:Sprite = new Sprite;
    			buttonSkin.graphics.beginFill( 0xff0000, 1.0 );
    			buttonSkin.graphics.drawRect(0,0,100,50);
    			buttonSkin.graphics.endFill();
    			buttonSkin.filters = [new BevelFilter()];
    			var textFormat:TextFormat = new TextFormat();
    			textFormat.color = 0xffffff;
    			textFormat.align = "center";
    			var textField:TextField = new TextField();
    			textField.defaultTextFormat = textFormat;
    			textField.text = "창을 감추기(1초후에 나타남)";
    			textField.width = 100;
    			textField.multiline = true;
    			textField.wordWrap = true;
    			textField.y = buttonSkin.height/2 - textField.textHeight/2; 
    			buttonSkin.addChild( textField );
    			var button:SimpleButton = new SimpleButton(buttonSkin,buttonSkin,buttonSkin,buttonSkin);
    			button.addEventListener(MouseEvent.CLICK, __onHide );
    			__window.stage.addChild( button );
    			
    			//Animation 동작 시키기 버튼 
    			buttonSkin = new Sprite;
    			buttonSkin.graphics.beginFill( 0x0000ff, 1.0 );
    			buttonSkin.graphics.drawRect(0,0,100,50);
    			buttonSkin.graphics.endFill();
    			buttonSkin.filters = [new BevelFilter()];
    			textFormat = new TextFormat();
    			textFormat.color = 0xffffff;
    			textFormat.align = "center";
    			textField = new TextField();
    			textField.defaultTextFormat = textFormat;
    			textField.text = "동작시키기(1 second)";
    			textField.width = 100;
    			textField.multiline = true;
    			textField.wordWrap = true;
    			textField.y = buttonSkin.height/2 - textField.textHeight/2; 
    			buttonSkin.addChild( textField );
    			button= new SimpleButton(buttonSkin,buttonSkin,buttonSkin,buttonSkin);
    			button.y = 52;
    			button.addEventListener(MouseEvent.CLICK, __onAnimate );
    			__window.stage.addChild( button );			
    						
    			__window.activate();
    			__window.addEventListener(Event.CLOSING,__onClosing);
    		}
    		private function __init():void {
    			NativeApplication.nativeApplication.addEventListener(Event.ACTIVATE, __onActive );
    			NativeApplication.nativeApplication.addEventListener(Event.DEACTIVATE, __onDeactive );
    		}
    		private function setFrameRate( frameRate:Number ):void {
    			stage.frameRate = frameRate;
    			trace( frameRate );
    		}
    		private function animate($duration:int = 1000):void {
    			trace( "동작시작");
    			setFrameRate( 50 );
    			__buffer = getTimer() + $duration;
    			if(!__isAnimating) {
    				stage.addEventListener(Event.ENTER_FRAME,__onEnterFrame);
    			}
    		}
    		private function active():void {
    			if(!__isAnimating) {
    				setFrameRate( ACTIVE );	
    			} 
    			trace( "active " );
    		}
    		private function deactive():void {
    			if (!__isAnimating) {
    				setFrameRate( (__window.visible) ? INACTIVE_VISIBLE : INACTIVE_INVISIBLE );
    			}
    			trace( "deactive " );
    		}
    		private function show():void {
    			__window.visible = true;
    			active();
    		}
    		private function hide():void {
    			__window.visible = false;	
    			deactive();
    		}
    		private function __onActive($event:Event):void {
    			__isActive = true;
    			active();
    		}
    		private function __onDeactive($event:Event):void {
    			__isActive = false;
    			deactive();
    		}
    		private function __onEnterFrame($event:Event):void {
    			if( __buffer < getTimer() ) {
    				trace( "동작끝");
    				stage.removeEventListener(Event.ENTER_FRAME,__onEnterFrame);
    				__isAnimating = false;
    				if( __isActive ) {
    					active();
    				} else {
    					deactive();
    				}
    			}
    		}
    		private function __onHide($event:MouseEvent):void {
    			hide();
    			setTimeout(show,1000);
    		}
    		private function __onAnimate($event:MouseEvent):void {
    			animate( 1000 );
    		}
    		private function __onClosing($event:Event):void {
    			NativeApplication.nativeApplication.exit();
    		}
    	}
    }
    


    참고글
    Reducing CPU usage in Adobe AIR 
    [머드초보]시스템트레이아이콘(system tray icon) 예제


    글쓴이 : 지돌스타(http://blog.jidolstar.com/622)


    Flash, Flex, AIR 개발자들이 한권정도는 꼭 가지고 있을 만한 책이다. 이 책은 콜린 무크의 유명한 책인 Essential ActionScript 3.0을 지금도 업계에서 활발히 일하고 계신 유윤선, 송호철님이 번역한 책이다. ActionScript 3.0에 관한한 거의 바이블 수준이다. 시간될 때마다 쭉 훑어보며 느낀 거지만 ActionScript 3.0에 대한 전반적인 개념을 습득하는데 이만한 책이 없다고 생각된다. 

    Flash, Flex, AIR를 개발할 때 가장 기초가 되는 개념은 ActionScript 3.0이다. ActionScript 3.0에 대한 전반적인 개념 및 지식없이 Flex에 접근하다 보면 결국 한계를 느끼게 될 것이다. 왜냐하면 Flex 프레임워크가 모든 기능을 수행해 줄 수 있도록 만들어진 것은 아니기 때문이다. Flex는 프레임워크로서 가치가 있는 것이지 모든 기능을 Flex에서 찾으면 안된다. 또한 Flash로 개발할 때도 마찬가지 인데 타임라인만 의지하다가 뭔가 고급기술을 다뤄야할 필요가 있을때 ActionScript 3.0을 알아야 가능해진다. Flash든 Flex든 AIR든... Flash Platform 개발자가 접근할 수 있는 가장 기초가 되는 것은 결국 ActionScript 3.0이다. (예외적으로 AIR의 경우 Javascript로 만들 수 있는데 이는 컴파일러를 지원하는 차원이지 근간은 ActionScript 이다.)  

    이 책의 한가지 단점이라면 Flash Player 10 API에 해당하는 내용까지 다루지는 않고 있다. 이와 관련된 내용은 여전히 온라인의 ActionScript 3.0 라이브 독을 참고해야한다.

    글쓴이 : 지돌스타(http://blog.jidolstar.com/615

    ByteArray.org에 기존 Flash Player 9에서 사용되는 corelib의 JPGEncoder보다 더 빠른 Flash Player 10용으로 제작된 JPGEncoder를 만들어 공개했다. 더 빠를 수 있었던 것은 VectorBitmapData.setVector() 때문이다. 기존 Array 형태보다 글쓴이 컴퓨터에서는 2.5배 빠르다고 한다. 아래 프로그램에서 테스트 할 수 있다. 2880 x 2800 비트맵데이타를 encoding 하도록 만들어졌다.

     

     

     

    필자는 아직 여러가지 문제로 Flash Player 10용으로 개발하지 못하고 있어 아쉽지만 위와 같은 기능을 앞으로 잘 사용하면 속도개선에 큰 도움이 될 것이라 판단이 된다.

     

    ActionScript 3.0의 ApplicationDomain은 로드한 SWF파일 내부에 정의된 Class를 참조하는데 유용하게 쓰인다.

     

    ApplicationDomain은 현재 hasDefinition(name:String):Boolean 과 getDefinition(name:String):Object 등 2개의 public 메소드를 지원한다. 이 메소드를 이용하여 ApplicationDomain내 정의된 클래스 정의 유무를 알 수 있고 그에 대한 참조를 얻을 수 있다. 다음은 사용 예이다.

     

    var myInstance:DisplayObject = new
    (ApplicationDomain.currentDomain.getDefinition("com.flassari.MyClass"));

     

    위 코드의 경우 현재 애플리케이션의 ApplicationDomain상에 정의된 com.flassari.MyClass 클래스의 정의를 읽어와 DisplayObject 객체를 만드는 것이다. 사용하기 매우 쉽다. 그러나 클래스의 이름을 알고 있다는 전재하에 쓰일 수 있는 방법이다. 그럼 ApplicationDomain 내에 정의된 이름을 알지 못하는 클래스들의 이름을 목록을 알고 싶을 때는 어떻게 해야할까? 불행히도 ApplicationDomain 클래스는 이것을 지원해주고 있지 않다. ApplicationDomain.getAllDefinitions():Array 와 같은 메소드가 지원된다면 좋을 텐데 말이다.

     

    해결방법이 있다. 바로 여기에서 소개하는 SwfClassExplorer 클래스를 이용하면 된다. 사용방법은 매우 단순하다. 하나의 static 메소드인 getClasses(bytes:ByteArray):Array 를 이용하면 된다. 아래 코드는 간단한 사용예제이다.

    public function init():void {
    	// Load the swf
    	var urlLoader:URLLoader = new URLLoader();
    	urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
    	urlLoader.addEventListener(Event.COMPLETE, onSwfLoaded);
    	urlLoader.load(new URLRequest("pathToSwf"));
    }
    
    private function onSwfLoaded(e:Event):void {
    	// Get the bytes from the swf
    	var bytes:ByteArray = ByteArray(URLLoader(e.currentTarget).data);
    	// And feed it to the SwfClassExplorer
    	var traits:Array = SwfClassExplorer.getClasses(bytes);
    	for each (var trait:Traits in traits) {
    		trace(trait.toString()); // The full class name
    	}
    }

     

    이는 매우 유용함을 알 수 있다. SWF를 Loader가 아닌 URLLoader로 로드했음을 볼 수 있다. 그리고 실제 DisplayObject 객체를 생성하기 위해 다음과 같이 코딩하면 되겠다.

     

    private var _loader:Loader;
    
    public function init():void {
    	var urlLoader:URLLoader = new URLLoader();
    	urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
    	urlLoader.addEventListener(Event.COMPLETE, onSwfLoaded);
    	urlLoader.load(new URLRequest("pathToSwf"));
    }
    
    private function onSwfLoaded(e:Event):void {
    	var bytes:ByteArray = ByteArray(URLLoader(e.currentTarget).data);
    	var traits:Array = SwfClassExplorer.getClasses(bytes);
    	for each (var trait:Traits in traits) {
    		trace(trait.toString()); // The full class name
    	}
    
    	selectedClassName = traits[0];
    	_loader = new Loader();
    	var loaderContext:LoaderContext = new LoaderContext();
    	_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaderInit);
    	_loader.loadBytes( bytes, loaderContext );
    }
    
    private function onLoaderInit(event:Event){
    	var obj:Object;
    	try {
    		obj = new (_loader.contentLoaderInfo.applicationDomain.getDefinition(selectedClassName ));
    	} catch (e:Error) {
    		trace( "Can't instantiate class:\n" + e.toString() );
    	}
    	if (obj != null && obj is DisplayObject) {
    		addChild( DisplayObject(obj) );
    	}
    }

     

    결국 Binay형태로 받아온 SWF를 Loader클래스의 loadBytes()메소드를 이용해 비동기적으로 로드처리한 뒤에 Loader.contentLoaderInfo.applicationDomain의 getDefinition() 메소드를 이용해 객체를 생성하는 방식이다.

     

    아쉬운 점은 여기서 loadBytes()를 이용한 비동기적 처리가 아닌 동기적 처리를 통해 클래스 참조를 얻어낼 수 있다면 더욱 좋을 것 같다.  사실 이 부분을 찾다가 이 예제를 찾아내게 된건데 매우 유용해서 포스팅 한 것이다.

     

    세부적인 예제 및 소스는 아래 출처를 참고한다.

     

    출처

    Swf Class Explorer for AS3

    소스

    위 출처에서 소스를 다운받을 수 없으면 아래 링크로 다운받는다.

     

     

    1. Application Domain 사용 예제

     

    지난 호에서 우리는 Application domain에 대해서 알아보았다. 지금까지 설명한 내용을 확인하기 위해 몇가지 예제를 만들고자 한다.

     

     [그림 1] 메인과 모듈에서 정의되어 있는 Sprite 클래스

     

    [그림 1]은 메인 프로그램과 모듈 프로그램에 각각 정의되어 있는 Sprite를 확장한 클래스를 보여주고 있다. 메인은 Yellow, Blue를 가지며 둥근 모양이다. 모듈은 Blue, Red, Green을 가지며 사각형 모양이다. 서로 클래스 명이 중복되는 것은 Blue이다.

     

    메인과 모듈에서 모두 아래와 같은 형태로 4개의 Sprite를 확장한 클래스의 정의를 참조한다.

    var ClassRef:Class =
    ApplicationDomain.currentDomain.getDefinition("클래스명") as Class;

     

    클래스 참조값을 이용해 인스턴스를 생성하여 메인과 모듈에서 그림을 그려준다. 모듈을 불러올 때 메인 프로그램의 Application domain의 설정에 따라 메인과 모듈에 정의된 Sprite 클래스들을 참조 권한이 달라지게 될 것이다.

     

    아래 코드는 모듈 프로그램에서 정의된 BlueSprite 클래스의 정의다. draw() 함수를 호출하면 30×30의 파란색 사각형을 그려준다.

     

     

    //BlueSprite.as – 모듈 프로그램에서 정의됨
    package com.jidolstar.blue
    {
            import flash.display.Sprite;
    
            public class BlueSprite extends Sprite
            {
                   public function BlueSprite()
                   {
                           super();
                   }
    
                   public function draw():void
                   {
                           graphics.clear();
                           graphics.beginFill(0x0000ff, 1);
                           graphics.drawRect(0,0,30,30);
                           graphics.endFill();
                   }
            }
    }

     

    아래 코드는 메인 프로그램에서 정의되는 BlueSprite이다. 모듈에서의 정의와 달리 draw()함수를 호출하면 파란색 원을 그려준다. 메인과 모듈에서 정의한 이름(BlueSprite)은 같지만 그 기능은 다르다는 것을 기억하자.

     

    //BlueSprite.as – 메인 프로그램에서 정의됨
    package com.jidolstar.blue
    {
            import flash.display.Sprite;
            public class BlueSprite extends Sprite
            {
                   public function BlueSprite()
                   {
                           super();
                   }
                   public function draw():void
                   {
                           this.graphics.clear();
                           this.graphics.beginFill(0x0000ff, 1);
                           this.graphics.drawCircle(15,15,15);
                           this.graphics.endFill();
                   }
            }
    }

     

    이처럼 메인과 모듈에 Sprite 확장 클래스를 만들어둔다.

    모듈 프로그램은 다음처럼 작성된다.

     

     

    //TestModule.as
    package {
    	import com.jidolstar.blue.BlueSprite;
    	import com.jidolstar.green.GreenSprite;
    	import com.jidolstar.red.RedSprite;
    
    	import flash.display.Sprite;
    	import flash.system.ApplicationDomain;
    
    	public class TestModule extends Sprite
    	{
    		private var blue:BlueSprite;
    		private var red:RedSprite;
    		private var green:GreenSprite;
    
    		public function TestModule()
    		{
    			super();
    
    			var BlueSpriteRef:Class;
    			var RedSpriteRef:Class;
    			var GreenSpriteRef:Class;
    			var YellowSpriteRef:Class;
    			var msg:String = "";
    			var sprite:*;
    			var x:int = 0;
    
    			try
    			{
    				var ClassRef:Class = ApplicationDomain.currentDomain.getDefinition("클래스명") as Class;
    				BlueSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.blue.BlueSprite") as Class;
    				sprite = new BlueSpriteRef;
    				sprite.x = x;
    				sprite.y = 10;
    				x += 40;
    				sprite.draw();
    				addChild( sprite );
    			}
    			catch (e:Error)
    			{
    				msg+= "Module에서 com.jidolstar.blue.BlueSprite 로드 실패n"
    			}
    
    			try
    			{
    				RedSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.red.RedSprite") as Class;
    				sprite = new RedSpriteRef;
    				sprite.x = x;
    				sprite.y = 10;
    				x += 40;
    				sprite.draw();
    				addChild( sprite );
    			}
    			catch (e:Error)
    			{
    				msg += "Module에서 com.jidolstar.red.RedSprite 로드 실패n"
    			}                       
    
    			try
    			{
    				GreenSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.green.GreenSprite") as Class;
    				sprite = new GreenSpriteRef;
    				sprite.x = x;
    				sprite.y = 10;
    				x += 40;
    				sprite.draw();
    				addChild( sprite );
    
    			}
    			catch (e:Error)
    			{
    				msg += "Module에서 com.jidolstar.green.GreenSprite 로드 실패n"
    			}       
    
    			try
    			{
    				YellowSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.yellow.YellowSprite") as Class;
    				sprite = new YellowSpriteRef;
    				sprite.x = x;
    				sprite.y = 10;
    				x += 40;
    				sprite.draw();
    				this.addChild( sprite );
    			}
    			catch (e:Error)
    			{
    				msg += "Module에서 com.jidolstar.yellow.YellowSprite 로드 실패n"
    			}
    
    			trace(msg);
    		}
            }
    }

     

    메인, 모듈에서 정의되는 BlueSprite, RedSprite, GreenSprite, YellowSprite 클래스 정의를 모듈의 current domain으로부터 얻어와 성공하면 그림을 그려주는 형태로 되어 있다. 특별히 모듈에서는 BlueSprite, RedSprite, GreenSprite가 정의되도록 아래와 같은 코드가 있다.

     

    private var blue:BlueSprite;
    private var red:RedSprite;
    private var green:GreenSprite;

     

    메인 프로그램은 모듈과 거의 동일한 모습으로 만들어진다. 다른 점은 모듈을 읽어올 수 있는 부분과 Application domain을 설정하는 부분이 추가가 된다. Application domain을 적용하는 상황이 총 세가지이므로 중복되는 코드를 줄이기 위해 Base 클래스를 만들고 이 Base 클래스를 확장해 3개의 상황에 맞게 메인 프로그램 A,B,C를 작성한다. 그럼 Base 프로그램을 보자.

     

    //MainAppBase.as
    package
    {
    import com.jidolstar.blue.BlueSprite;
    import com.jidolstar.yellow.YellowSprite;
    
    import flash.display.DisplayObject;
    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.net.URLRequest;
    import flash.system.ApplicationDomain;
    import flash.system.LoaderContext;
    import flash.text.TextField;
    import flash.text.TextFormat;
    
    public class MainAppBase extends Sprite
    {
    	private var blue:BlueSprite;
    	private var yellow:YellowSprite;
    	private var loader:Loader;
    	protected var applicationDomain:ApplicationDomain;
    
    	public function MainAppBase()
    	{
    		super();
    
    		loader = new Loader();
    		var request:URLRequest = new URLRequest("TestModule.swf");
    		var context:LoaderContext = new LoaderContext();
    		context.applicationDomain = applicationDomain;
    		loader.contentLoaderInfo.addEventListener( Event.COMPLETE, completeHander );
    		loader.load(request,context);
    	}
    
    	private function completeHander(event:Event):void
    	{
    		var BlueSpriteRef:Class;
    		var RedSpriteRef:Class;
    		var GreenSpriteRef:Class;
    		var YellowSpriteRef:Class;
    		var msg:String = "";
    		var sprite:*;
    		var x:int = 0;
    		var ModuleRef:Class = loader.contentLoaderInfo.applicationDomain.getDefinition("TestModule") as Class;
    		var module:DisplayObject = new ModuleRef();
    		//var module:DisplayObject = event.target.content as DisplayObject
    		module.x = 0;
    		module.y = 50;
    		this.addChild( module );
    
    		loader.contentLoaderInfo.removeEventListener( Event.COMPLETE, completeHander );
    		loader.unload();
    		loader = null;
    
    		try
    		{
    			BlueSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.blue.BlueSprite") as Class;
    			sprite = new BlueSpriteRef;
    			sprite.x = x;
    			sprite.y = 10;
    			x += 40;
    			sprite.draw();
    			this.addChild( sprite );
    		}
    		catch (e:Error)
    		{
    			msg+= "Main에서 com.jidolstar.blue.BlueSprite 로드 실패n"
    		}
    
    		try
    		{
    			RedSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.red.RedSprite") as Class;
    			sprite = new RedSpriteRef;
    			sprite.x = x;
    			sprite.y = 10;
    			x += 40;
    			sprite.draw();
    			this.addChild( sprite );
    		}
    		catch (e:Error)
    		{
    			msg += "Main에서 com.jidolstar.red.RedSprite 로드 실패n"
    		}                       
    
    		try
    		{
    			GreenSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.green.GreenSprite") as Class;
    			sprite = new GreenSpriteRef;
    			sprite.x = x;
    			sprite.y = 10;
    			x += 40;
    			sprite.draw();
    			this.addChild( sprite );
    		}
    		catch (e:Error)
    		{
    			msg += "Main에서 com.jidolstar.green.GreenSprite 로드 실패n"
    		}       
    
    		try
    		{
    			YellowSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.yellow.YellowSprite") as Class;
    			sprite = new YellowSpriteRef;
    			sprite.x = x;
    			sprite.y = 10;
    			x += 40;
    			sprite.draw();
    			this.addChild( sprite );
    		}
    		catch (e:Error)
    		{
    			msg += "Module에서 com.jidolstar.yellow.YellowSprite 로드 실패n"
    		}
    
    		trace(msg);
    	}
    }
    }

     

     

    completeHandler() 함수를 보면 모듈 프로그램에서 정의했던 것과 별반 다를 바 없다. 단, 불러온 모듈을 화면에 표시하기 위해 아래와 같은 코드가 추가가 되어 있다.

     

    var module:DisplayObject = event.target.content as DisplayObject
    module.x = 0;
    module.y = 50;
    this.addChild( module );

     

     

    생성자에서 Loader 클래스를 이용해 해당 모듈 SWF를 불러오도록 만들었다. Application domain을 protected로 값을 받아 설정할 수 있도록 만든 것을 확인하기 바란다. 이렇게 만든 의도는 이 Base 클래스를 확장한 메인 프로그램의 생성자에 application domain을 미리 설정하라는 것을 의미한다.

     

    아래는 앞서 만든 Base 클래스를 이용해 application domain을 새롭게 정의해서 사용하는 예이다. 생성자에 applicationDomain을 어떻게 설정했는지 확인하면 되겠다.

     

    //MainAppUsageA.as
    package {
    	import flash.system.ApplicationDomain;
    	public class MainAppUsageA extends MainAppBase
    	{
    		public function MainAppUsageA()
    		{
    			applicationDomain = new ApplicationDomain();
    			super();
    		}
    	}
    }
    //MainAppUsageB.as
    package {
    	import flash.system.ApplicationDomain;
    	public class MainAppUsageA extends MainAppBase
    	{
    		public function MainAppUsageA()
    		{
    			applicationDomain = ApplicationDomain.currentDomain;
    			super();
    		}
    	}
    }
    //MainAppUsageC.as
    package {
    	import flash.system.ApplicationDomain;
    	public class MainAppUsageA extends MainAppBase
    	{
    		public function MainAppUsageA()
    		{
    			applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
    			super();
    		}
    	}
    }

     

    위에서 제시한 코드는 두 개의 ActionScript3 프로젝트를 만들어서 아래와 같은 구성으로 만든다.

     

    [그림 2] 메인과 모듈 프로젝트 구성

     

    위처럼 모듈 SWF파일을 메인 쪽에 복사해야 모듈을 불러서 동작시킬 수 있다. 3개의 메인 프로그램을 단독으로 실행할 수 있도록 하기 위해, 프로젝트 폴더 명에서 마우스 오른쪽 버튼을 눌러 컨텍스트 메뉴의 Properties를 클릭해서 뜨는 창의 좌측에 ActionScript Applications에 default로 지정된 것 말고도 나머지 2개를 추가해준다.

     

    이렇게 하면 기본적인 테스트 환경이 완료가 된다. 위의 MainAppUsageA.as를 선택하고 실행해보자.

     

    [그림 3] 메인과 모듈이 서로 다른 application domain 일 때 실행 화면

     

    이것은 new ApplicationDomain()를 이용해 모듈의 application domain을 설정했을 때 결과이다. 메인과 모듈간에 application domain이 다르기 때문에 상대방에서 정의한 클래스를 참조할 수 없다. 윗부분에 메인부분이다. 메인에서 정의한 BlueSprite와 YellowSprite에 대한 정의만 사용하여 그림을 그렸다. 아래 부분은 모듈로 모듈에서 정의한 BlueSprite, RedSprite, GreenSprite 를 이용해 그림이 그려졌다는 것을 알 수 있다.

    MainAppUsageB.as를 선택하고 실행해보자.

     

     

    [그림 4] 메인과 모듈이 서로 같은 application domain 일 때 실행 화면

     

    이것은 ApplicationDomain.currentDomain을 이용해 모듈의 application domain을 설정했을 때 결과이다.

    위쪽의 메인 프로그램에서 그려준 것을 보면 자신이 정의한 BlueSprite와 YellowSprite를 이용해 둥근 모양의 그림을 그렸다. 또한 같은 application domain이므로 모듈에서 정의한 RedSprite와 GreenSprite를 이용해 사각형 모양의 그림도 그렸다. 메인 프로그램과 모듈에는 BlueSprite가 중복되었다는 것을 [그림 2]에서 미리 언급했었다. 이 경우 메인에 이미 BlueSprite가 정의된 상태였기 때문에 모듈의 BlueSprite는 무시되었다는 것을 확인할 수 있다.

     

    MainAppBase.as의 completeHandler() 함수를 잘 살펴보면 loader.unload()를 호출하고 나서 모듈의 SWF파일을 Unload했음에도 불구하고 모듈에서 정의한 RedSprite, GreenSprite를 그대로 사용하고 있다는 것을 볼 수 있다. 즉, 부모의 application domain에 등록된 자식의 Class 정의는 그 자식이 Unload되더라도 가비지 컬렉션 대상이 되지 않는다.

     

    마지막으로 MainAppUsageC.as를 선택하고 실행해보자.

     

     

    [그림 5] 메인과 모듈이 application domain이 부모-자식 관계일 때 실행 화면

     

    이것은 new ApplicationDomain(ApplicationDomain.currentDomain)를 이용해 모듈의 application domain을 설정했을 때 결과이다. Application domain이 부모-자식관계가 되어 메인 프로그램에서는 모듈에서 정의한 클래스를 참고할 수 없으므로 RedSprite와 GreenSprite 클래스의 정의를 읽을 수 없다. 하지만 모듈은 자식 application domain 에 있기 때문에 부모의 BlueSprite와 YellowSprite를 사용한다. 중복정의된 BlueSprite는 이미 메인 프로그램에 정의되어 있던 BlueSprite로 대체되어진다.

     

     

    2. 정리하며

     

    Application domain에 대해서 자세하게 알아보았다. 모듈화 프로그래밍을 하기 위해서는 반드시 숙지해야 하는 부분이다. ActionScript 3.0로 모듈 프로그래밍을 할 때는 Loader 클래스를 이용해야하는데 상당히 불편한 점이 많다. 하지만 Flex에서는 Module 및 ModuleManager, ModuleLoader 등을 이용해 모듈 프로그래밍을 더욱 간편하게 할 수 있도록 도와주고 있다. 하지만 내부적으로는 Loader 클래스를 사용하고 있기 때문에 Application domain의 개념을 알고 있어야 이 클래스들을 적절히 잘 사용할 수 있다.

     

    <참고내용>

     

    이 글은 adobeflex.co.kr에 기술문서로 올린 글입니다.

    1. Application domain에 대해

    1.1 모듈화와 모듈화에 따른 application domain의 역할

     

    프로젝트를 하다 보면 모듈화를 할 필요가 생긴다. 모듈화는 하나의 메인 응용 프로그램에 모든 기능이 있는 대신, 관련된 기능을 떼어내어 모듈을 만들어 필요한 시점에 동적으로 불러 사용할 수 있도록 만드는 것을 의미한다.

     

    가령 Flex로 만든 블로그가 있다고 가정하면. 블로그에는 글쓰기, 리스트, 방명록, 관리, 메뉴 등이 존재할 것이다. 이러한 것이 하나의 SWF 파일에 모두 존재한다면 보여주지도 않을 방명록, 관리 부분까지 처음 프로그램 실행부터 전부 불러와야 하는 부담이 생긴다. 그래서 필요에 따라 동적으로 읽어서 사용할 수 있도록 글쓰기에 관련된 SWF, 리스트에 관련된 SWF 등으로 나눠서 만들어 놓으면 필요할 때 사용할 수 있으므로 더욱 효율적일 것이다.

     

    이렇게 모듈화를 할 경우 블로그 메인 프로그램과 각 글쓰기 모듈 프로그램, 리스트 모듈 프로그램, 방명록 모듈 프로그램간에 통신을 하게 될 것이고, 때에 따라서는 메인 프로그램에서 정의한 자원을 모듈에 포함할 것 없이 재사용할 필요도 있을 것이다. 이렇게 하면 모듈에 메인 프로그램과 동일한 중복 코드를 가지고 있을 필요가 없어 용량이 줄어들 수 있기 때문이다. 그렇게 되면 자연스럽게 메인-모듈, 모듈-모듈간에 자원을 공유하는 일이 발생하게 된다.

     

    이 문서는 모듈화를 하는 방법을 제시해준다기 보다는 모듈화를 위해 선행적으로 알아야 할 한가지를 언급하고자 한다. 그 중 하나가 바로 여기서 지금부터 다룰 application domain(응용 프로그램 영역)이다. application domain은 서로 다른 SWF 프로그램끼리 자원 공유를 위한 영역을 구분 지을 수 있도록 도와준다.

     

    Namespace와의 비교

     

    application domain에 대해서 이해하기 위해 Namespace와 비교해 보도록 하겠다.

     

    Namespace는 이름, 용어 혹은 단어 항목들을 위한 문맥(context)을 제공하는 추상적인 컨테이너(container)라고 정의되어 있다. Namespace는 Java나 Flex, ActionScript3에서 사용하는 패키지(Package)와 거의 동일하다. Namespace는 프로그램 안에 같은 이름의 Class가 있을 때 구분해주는 역할을 한다. 가령 com.jidolstar.URLUtil과 flash.jidolstar.URLUtil 이 있다면 URLUtil로 이름은 같지만 Namespace는 다르므로 이 클래스는 이름만 같지 전혀 다른 클래스이다. 이렇듯 Namespace를 잘 활용하면 같은 이름의 클래스로 다른 동작을 하도록 만들 수 있고 또는 프로젝트 진행시 클래스 명이 충돌하는 경우도 방지할 수 있다.

     

    그럼 application domain의 경우는 어떨까? Namespace는 단일 응용 프로그램에 같은 이름의 Class를 구분하기 위해 사용되는 반면 application domain은 여러 개의 응용 프로그램간에 같은 이름의 Class를 구분하기 위해 사용된다.

     

    프로그램들이 하나의 application domain안에 포함되어 있다면 같은 이름을 가진 Class는 단 한 개만 존재가 가능하다. 가령 부모 프로그램의 Class 정의만 남고 자식 프로그램의 Class는 무시된다. 반면 프로그램들의 application domain이 서로 다르다면 같은 이름을 가진 Class라 할지라도 이들 Class는 서로 다른 application domain에 존재하는 것이므로 다른 것으로 해석된다.

     

     

    1.2 Application domain 개념

     

    아래는 application domain에 대해서 간단히 설명하고 있다.

    application domain은 Loader의 load() 또는 loadBytes() 메쏘드를 이용해 ActionScript3.0으로 만들어진 SWF 파일을 불러들일 때만 사용된다.

    application domain를 적용하기 위해 ApplicationDomain 클래스를 LoaderContext 클래스의 applicationDomain 속성에 대입하여 Loader의 load() 및 loadBytes() 메쏘드를 호출시 인자로 LoaderContext의 인스턴스를 넘겨주면 된다.

    application domain은 동일한 보안 도메인(security domain)안에서 하나 이상의 SWF 파일 안에 제작된 ActionScript3.0 코드들을 목록화하여 관리한다. 다시 말하면 하나의 application domain안에서 여러 개의 SWF 파일에서 작성한 ActionScript3.0 코드를 관리할 수 있다. 또한 서로 다른 SWF 파일간에 다른 application domian을 안에 있다면 SWF 파일에 작성된 ActionScript3.0 코드는 중복되어지지 않는다. ActionScript3.0 코드는 Class 형태로 제작되며 결과적으로 application domain을 이용해 SWF 파일에 정의된 Class들이 서로 같은 영역에서 정의될 수 있고 또는 다른 영역에서 정의될 수도 있다. 또한 부모 SWF 파일에서 자식 SWF 파일에만 Class 정의 사용할 수 있도록 허용할 수 있다.

    위처럼 application domain은 SWF 프로그램에 ActionScript 3.0으로 제작된 Class 정의를 원하는 영역(도메인)에 나누는 역할을 한다. 어도비 라이브 독(Adobe Livedocs)에 의하면 application domain에 대해서 아래 중요한 사실을 언급하고 있다.

     

    1. SWF 파일 안에 있는 모든 코드는 하나의 application domain 안에 존재되도록 정의된다. 메인 응용 프로그램(main application)의 application domain은 current domain이다. system domain current domain을 비롯하여 모든 application domain을 포함하기 때문에 모든 Flash Player 클래스를 가지고 있다.
    2. system domain을 제외한 모든 application domain은 그와 연결된 parent domain을 가진다. 메인 응용 프로그램의 application domain에 대한 parent domain은 system domain이다. 같은 application domain안에서 부모 프로그램과 자식 프로그램이 있다고 가정하자. 이때 부모 프로그램이 불러들인 자식 프로그램 안에 클래스들은 그 부모 안에 같은 이름의 클래스가 정의되어 있지 않을 때만 정의되고 사용할 수 있다. 또한 자식 프로그램의 클래스가 부모의 있는 클래스를 재정의(override) 할 수 없다.

     

    같은 보안 도메인(scrutiny domain) 안에서 LoaderContext의 Application Domain 속성에 자신이 사용하고자 하는 Application Domain 속성을 아래 4가지 중 하나를 선택하면 되겠다.

     

    1. 부모의 application domain을 상속받은 application domain
      이것이 기본값이다.
      이 선택은 new ApplicationDomain(ApplicationDomain.currentDomain) 구문을 이용하는 것이다.
      이 방식은 자식의 application domain에서 부모에서 정의한 Class를 직접 사용할 수 있다. 가령 자식에서 ApplicationDomain.currentDomain.getDefinition(“부모에서 정의된 클래스명”)로 접근이 가능하다는 것을 의미한다. 반면 부모는 자신의 application domain에서 자식에서 정의된 Class에 접근이 불가능하다.
      이 방식의 장점은 자식에서 부모에 이미 정의된 Class와 이름이 같은 Class를 정의해도 오류가 나지 않는다. 자식은 해당 Class에 대한 부모의 정의를 상속받기만 하며, 부모와 충돌하는 클래스 정의는 무시된다.
    2. 부모 자신의 application domain
      ApplicationDomain.currentDomain 구문을 사용하는 것과 같다. 자식 SWF 파일을 불러오면 부모와 자식이 상대방에서 정의한 Class에 직접 접근이 가능하다. 서로 ApplicationDomain.currentDomain.getDefinition()을 이용해 접근이 가능하다. 단, 자식에서 부모와 같은 Class를 정의를 사용했다면 자식의 Class 정의는 무시된다.
    3. 시스템 application domain을 상속받은 application domain
      이는 new ApplicationDomain(null) 구문을 사용하는 것과 같다. 이렇게 하면 부모와 자식간에 정의된 Class는 서로 다른 application domain에 있기 때문에 자신의 application domain 안에서는 상대방에서 정의한 Class를 참고할 수 없게 된다. 부모와 자식간에 같은 이름의 Class가 있더라도 application domain이 다르므로 자식의 Class정의가 무시되는 경우는 없다.
    4. 임의의 application domain을 상속받은 application domain
      이 방법은 application domin 계층 구도가 복잡해질 수 있으므로 지양하는 방법이다. 자식 SWF을 자신의 security domain 안에 있는 임의의 application domain을 이용해 사용하는 것으로 가령, new ApplicationDomain(ApplicationDomain.currentDomain.parentDomain.parentDomain)을 사용하면 자식 SWF이 현재 application domain의 부모의 부모 domian을 상속받아 application domain을 만든다.

     

    때때로 부모와 자식이 상대방의 application domain에 접근해야 하는 경우도 있다. 상대방이 application domain에 접근할 수 있는 경우 ApplicationDomain.currentDomain으로 상대방의 Class 정의를 가져올 수 있다. 또 다른 경우 부모가 자식 SWF를 로드가 완료했을 때, Loader 함수의 인스턴스가 loader라면 loader.contentLoaderInfo.applicationDomain.getDefinition()를 이용해서 자식의 application domian에 접근하여 자식에서 정의한 Class를 사용할 수 있다. 자식이 로드되는 방식을 알고 있는 경우 부모의 application domian에 접근할 수 있는데 기본적으로 ApplicationDomain.currentDomain.parentDomin을 이용하여 부모에서 정의한 Class에 접근이 가능하겠다.

     

     

    2. ApplicationDomain Class 사용법에 따른 동작방식 해석

     

    아래 <그림 1>은 단일 도메인(domain1.com)에 존재하는 다양한 SWF 프로그램(module1.swf, module3.swf, application2.swf)을 메인 SWF 프로그램(application1.swf)에서 Loader 클래스의 인스턴스를 통해 불러서 사용하는 모습을 묘사하고 있다. 이때 불러오는 SWF 프로그램들은 각자의 application domain에 등록되어 있다는 것을 알 수 있다.

     


    <그림 1> Application domain과 SWF 프로그램 간의 관계

     

     

    아래 예제는 [그림 1]의 Usage B의 경우와 같다.

     

    package
    {
        import flash.display.Loader;
        import flash.display.Sprite;
        import flash.events.*;
        import flash.net.URLRequest;
        import flash.system.ApplicationDomain;
        import flash.system.LoaderContext;
    
        public class ApplicationDomainExample extends Sprite
        {
            private var ldr:Loader;
            public function ApplicationDomainExample()
            {
                ldr = new Loader();
                var req:URLRequest = new URLRequest("Greeter.swf");
                var ldrContext:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
                ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
                ldr.load(req, ldrContext);    
    
            }
    
            private function completeHandler(event:Event):void
            {
                var ClassRef:Class = ApplicationDomain.currentDomain.getDefinition("Greeter") as Class;
                var myGreeter:* = new ClassRef();
                var message:String = myGreeter.welcome("Tommy");
                trace(message); // Hello, Tommy
            }
        }
    }

     

    위 예제를 간단하게 설명하면 메인 프로그램에서 모듈 SWF를 불러올 때 메인과 같은 application domain으로 설정한다.  모듈 SWF 로드를 완료하면 모듈 SWF 파일에 정의된 클래스(Greeter)를 이용해 인스턴스를 생성하고  그 클래스의 메서드에 접근하는 것을 확인할 수 있다.

     

    이처럼 부모(메인)가 Loader 클래스를 이용해서 ActionScript 3.0으로 작성된 외부 SWF 파일을 자식으로 불러와 자식의 클래스와 메서드에 접근할 수 있다. Loader 클래스의 context 속성에 LoaderContext 객체를 넘겨주는데 LoaderContext에 바로 applicationDomain 매개변수를 넣을 수 있다. 위 예제에서는 메인 프로그램의 application domain인 current domain을 매개변수로 넘겨주었다.  자식의 SWF 파일에 부모와 동일한 application domain을 적용하겠다는 것을 의미한다. 자식을 부모의 application domain에 포함했기 때문에 부모는 자식에서 정의한 Greeter Class를 부모의 application domain 안에서 사용할 수 있게 된다.

     

    위의 과정에 대해서 좀더 자세히 알아보자. <그림 1>에서 application domain 사용 예가 3가지 있다는 것을 알 수 있다. 아래 <그림 2>는 application domain 안에서 어떻게 Class가 공유되는지 살펴보기 위한 것이다.

     

    <그림 2> application domain을 지정하는 3가지 방법

     

     

    <그림 2>에서 클래스 명에 X표는 로드 후 정의에서 무시되는 클래스라는 것만 일단 기억하자. 지금부터 <그림 2>에서 표현하고 있는 application domain의 3가지 사용법에 대해서 언급하고 그에 따른 동작방식에 대해 이해해 보겠다.

     

     

    2.1 ApplicationDomain 사용법A 새로운 application domain 사용

    var loader:Loader = new Loader();
    var request:URLRequest = new URLRequest("application2.swf");
    var context:LoaderContext = new LoaderContext();
    context.applicationDomain = new ApplicationDomain();
    loader.load(request,context);

     

    위 코드는 메인 프로그램인 application1.swf 내에서 짜여진다. 이는 메인 SWF 프로그램과 전혀 다른 application domain 안에 존재하도록 만든다. 불러오는 컨텐츠의 이름이 module2.swf가 아닌 application2.swf로 표현한 이유는 서로 다른 application domain이기 때문에 독립적인 실행이 가능할 수 있다는 전제가 들어갈 수 있기 때문이다. application2.swf의 parent domain은 system domain이 된다.

     

    <그림 3> 다른 application domain으로 외부 SWF를 불러오는 경우

     

    <그림 3>에서 application1.swf와 application2.swf가 완전히 다른 application domain이기 때문에 서로의 각자 영역에서 정의된 com1::Class1과 com2::Class2는 상대방의 프로그램에 의해 영향을 받지 않는다. 같은 클래스 이름을 가지는 com1::Class의 경우 각자 다른 영역에 존재하므로 이름만 같은 다른 클래스로 이해할 수 있다.

     

    var ClassRef:Class = getDefinitionByName("com1.Class1") as Class;

     

    참고로 위에서 사용된 getDefinitionByName() 함수는 내부적으로 ApplicationDomain.currentDomain.getDefinition()을 호출하도록 만들어져 있다.

    만약 application1.swf에서 위와 코드와 같이 접근하면 application2.swf를 불러온 상태에서도 application1.swf에 정의한 com1.Class1에 접근하게 된다. 반대로 application2.swf에서 같은 방식으로 접근할 때 application1.swf로 불려졌다 할지라도 서로 다른 application domain이므로 application2.swf의 com1.Class1에 접근하게 된다. 즉, 자기 자신의 application domain 영역에 정의된 Class를 사용하며 서로 간섭을 일으키지 않는다.

     

    var ClassRef:Class = getDefinitionByName("com2.Class2") as Class;

     

    위의 접근 방식은 <그림 3>에서 application2.swf에서만 사용이 가능하다. com2.Class2는 application2.swf에서 정의되었고 application1.swf의 application domain이 application2.swf의 것과 다르기 때문에 application1.swf에서는 이 Class를 사용할 수 없다. 완전히 다른 application domain 영역이므로 unload시 바로 가비지 컬렉션 대상이 된다.

     

    application1.swf에서 application2.swf의 com2.Class2에 접근하기 위해서는 application2.swf의 application domain의 참조를 알아야하는데 아래와 같이 참조가 가능하겠다.

    var ClassRef:Class = loader.contentLoaderInfo.applicationDomain.getDefinition("com2.Class2") as Class;

     

     

    2.2 ApplicationDomain 사용법B 부모의 current domain을 사용

     

    var loader:Loader = new Loader();
    var request:URLRequest = new URLRequest("module1.swf");
    var context:LoaderContext = new LoaderContext();
    context.applicationDomain = ApplicationDomain.currentDomain;
    loader.load(request,context);

     

    위 코드에서 module1.swf는 부모와 같은 application domain을 사용하고 있다. 이것은 부모 프로그램의 자원을 공유하겠다는 의미와 동일하다.

     

    <그림 4> 부모 프로그램과 동일한 application domain을 사용하는 경우

     

    <그림 4>에서 볼 수 있듯이 동일한 application domain에 있기 때문에 자식인 module1.swf는 부모 application1.swf 내에 정의된 클래스를 사용할 수 있다. 만약 클래스가 부모 프로그램에 정의되어 있지 않는 경우 자식 프로그램에서 정의된 클래스가 부모 프로그램의 application domain에 등록되어 함께 공유하게 된다. 자식 프로그램과 부모 프로그램에 같은 이름의 Class가 존재한다면 자식 프로그램에서 정의한 Class는 무시된다.

     

    var ClassRef:Class = getDefinitionByName("com1.Class1") as Class;

     

     

    위와 같이 양쪽 프로그램에서 클래스 정의를 참조한다고 할 때 이미 application1.swf에 com1.Class1의 정의가 존재한 상태에서 module1.swf가 불려온 경우 module1.swf에 정의된 com1.Class1은 무시가 되어버린다. 결과적으로, 양쪽 프로그램에서 getDefinitionByName(“com1.Class1”)에 의해 얻어진 Class참조는 바로 application1.swf에 있는 com1.Class1이다.

     

    var ClassRef:Class = getDefinitionByName("com2.Class2") as Class;
    var ClassRef:Class = loader.contentLoaderInfo.applicationDomain.getDefinition("com2.Class2") as Class;

     

    위 경우 두가지 모두 똑같은 com2.Class2의 클래스 정의를 얻는 방법이다. 같은 application domain에 있으므로 가능한 것이다. 단, application1.swf가 module1.swf를 로드하기 전에는 이와 같이 사용할 수 없다. 왜냐하면 application1.swf의 application domain에는 com2.Class2가 정의되어 있지 않기 때문이다. 하지만 module1.swf을 로드한 후에는 같은 application domain에 com2.Class2가 등록이 된다. 그러므로 application1.swf에서도 module1.swf에서 정의한 com2.Class2 클래스 정의를 이용할 수 있다.

     

    이러한 방법은 RSLs(Runtime Shared Libraries)에서 사용되며 로드된 SWF는 RSL로 처리된다. 이 말은 RSL과 동일하게 가비지 컬렉션 대상이 될 수 없다는 것을 의미한다. 왜냐하면 메인 프로그램인 application1.swf의 application domain에 로드된 module1.swf의 클래스 정의가 이미 부모의 application domain 내에 등록되었기 때문에 자식인 module1.swf의 참조를 지우고 언로드(unload)하더라도 한번 등록된 Class 정의는 지워지지 않는다.

     

    RSL과 다른 점은 RSL은 프로그램 실행시 특별한 코딩이 필요 없이 자동으로 로드하는 반면 이 방법은 필요한 시점에 동적 로드가 가능하다는 것이다. 자동으로 로드한 다는 것은 Flex 프레임워크가 자체 지원하고 있다는 것을 의미한다.

     

     

    2.3 ApplicationDomain 사용법C 부모의 current domain를 상속받은 새로운 application domain 사용

     

    var loader:Loader = new Loader();
    var request:URLRequest = new URLRequest("module3.swf");
    var context:LoaderContext = new LoaderContext();
    context.applicationDomain =
    new ApplicationDomain(ApplicationDomain.currentDomain);
    loader.load(request,context);

     

    new ApplicationDomain()시 인자는 parentDomain이다. 즉, 새로운 application domain을 작성하되 부모의 current domain을 인자로 넘겨준다.

     

     

    <그림 5> 부모의 application domain을 이용해 새로운 자식 application domain을 생성하는 경우

     

     

    이렇게 되면 application domain 자체가 부모-자식관계가 된다. <그림 5>에서 보면 application domain이 걸쳐있는 것을 볼 수 있다.

     

    var ClassRef:Class = getDefinitionByName("com1.Class1") as Class;

     

    application1.swf가 module3.swf를 불러온 후 com1.Class1을 접근하면 자식 프로그램인 module3.swf에서 정의된 com1.Class1은 무시되어 버린다. 왜냐하면 application1.swf에 이미 com1.Class1이 정의되어 있고 module3.swf은 application1.swf의 같은 application domain을 참고하기 때문이다.

     

    var ClassRef:Class = getDefinitionByName("com2.Class2") as Class;

     

    재미있는 사실은 이 경우이다.

     

    application1.swf가 module3.swf를 불러왔지만 application1.swf에서 정의되지 않는 com2.Class2를 module3.swf에서 정의한 com2.Class2를 application1.swf의 application domain 에서는 이 클래스 정의를 참조할 수 없다. com2.Class2 정의는 자식 프로그램인 module3.swf의 application domain에서만 참조가 가능하다.

     

    이 관계를 쉽게 이해하기 위해 Class를 상속받는 관계와 비교해 생각하면 된다. 자식은 부모에 있는 모든 자원을 사용할 수 있으나 부모는 자식에서 새로 정의된 자원은 사용할 수 없다라는 사실을 기억하면 이러한 관계가 쉽게 이해될 수 있을 것이다.

     

    이 방법은 부모에서 정의된 클래스를 최대한 활용하면서 자식의 독립된 클래스 영역을 확보하는데 사용된다. 자식에서 정의된 클래스가 부모의 application domain에 포함되지 않으므로 부모가 자식의 SWF에 대한 참조를 포함하지 않는 경우, 자식에서 정의한 클래스는 모두 가비지 컬렉션 대상이 된다.

     

     

    2.4 Application domain의 사용법에 따른 동작방식 표

     

    지금까지 application domain을 적용하는 방법과 그에 따른 결과가 어떻게 되는지 알아보았다. 관련된 내용을 <표 1>에 정리해 보았다. 참고하길 바란다.

     

    [표 1] Application domain 사용법에 따른 동작방식

     

    3. Application Domain의 다양한 응용

    앞에서 application domain의 사용법 및 동작방식에 대해서 알 수 있었다. 많은 경우 이 3가지 방법을 섞어가며 사용하게 될 것이다. 아래 몇 가지 예제를 통해서 어떻게 자원이 공유되는지 이해해보자.

     

    <그림 6> application domain 응용 예시 도면 1

     

    <그림 6>을 보자. 부모 프로그램인 application1.swf의 application domain안에서 module1.swf가 먼저 로드되고 module2.swf가 나중에 로드된다.

     

    처음 로드될 때를 살펴보자. 앞에서 이미 언급한 것과 동일하게 module1.swf의 com1.Class1는 application1.swf에 이미 정의되어 있기 때문에 무시된다. 하지만 com2:Class2는 정의되어 있지 않았기 때문에 application domain에 등록되고 공유된다.

     

    나중 로드될 때를 보자. module2.swf 에는 application1.swf에서 정의된 com1.Class1이 있으므로 무시된다. 또한 이미 module1.swf에 의해 application domain에 com2.Class2 등록되어 있으므로 module2.swf의 com2.Class2는 무시되어 버린다.


     

    <그림 7> application domain 응용 예시 도면 2

     

    <그림 7>의 경우 앞에서 언급한 것과 같은데 다른 점은 module이 그의 application domain안에서 다른 module을 불러들이고 있다.

     

    메인 프로그램인 application1.swf에 이미 com1.Class1이 정의되어 있으므로 모듈에서 정의된 모든 com1.Class1은 무시된다. 두 모듈은 전부 application1.swf의 com1.Class1의 정의를 사용할 수 있다.

    모두 로드가 완료된 경우에 application1.swf는 module4.swf의 com3.Class3를 사용할 수 없지만 module3.swf는 사용할 수 있다. 왜냐하면 application1.swf과 module4.swf의 application domain관계는 부모-자식 관계이기 때문이다.

     

    <그림 8> application domain 응용 예시 도면 3

     

    <그림 8>에서 Application domain은 앞에서 언급한 것과 동일하게 적용된다.

     

    module5.swf에 다른 application domain을 가진 application2.swf가 로드되는 경우 메인 프로그램(application1.swf)과 module5.swf와 전혀 다른 영역을 가지므로 application2.swf안에 정의된 모든 클래스는 application1.swf와 module5.swf에서 정의된 클래스들과 별개가 된다.


    이 글은 adobeflex.co.kr의 기술문서로 올린 글이다.

     

     

     

    2008년 10월부터 공개했던 파일업로더를 기능 추가해 다시 공개합니다. 따로 ASdocs를 마련하지 않았기 때문에 불편하긴 하지만 차차 넣을 생각이고 응용프로그램도 몇개 더 만들 예정입니다.

     

    이번에 추가된 기능은 “업로드 금지 파일”을 등록하는 기능입니다. 가령, 서버측에 등록되면 민감할 php, js, asp, jsp, pl등의 확장자를 가진 파일을 업로드하려고 할 때 클라이언트 측에서 업로드하는 것을 원천적으로 방지하는 기능입니다.

     

    사용하는 방법은 예제 프로그램에서 uploader/html-template/index.template.html 안에 flashvars의 banfileExtensions를 참고하시면 되겠습니다. 금지할 파일 확장자를 “php;js;asp;” 형태로 만드시면 됩니다. 사용자가 여기에 등록된 파일을 올리려고 하는 경우 fileuploader_banFileExtension 이벤트가 발생하게 되며 이벤트가 발생할 때 처리를 하기 위해 FAService의 addEventListener()를 이용해 이벤트 핸들러 함수를 등록합니다. 이벤트 핸들러 함수에 넘어오는 인자값은 object형태로 아래와 같은 값이 넘어옵니다.

     

    {”filename”:업로드금지 처리된 파일명, “banFileExtension”: 업로드 금지처리된 파일의 확장자, “banFileExtensions”: 등록된 업로드 금지 확장자들. ;로 구분됨 }

     

    질문 및 버그에 대한 댓글은 언제든지 환영합니다.

     

    멀티 파일 업로더 실행 동영상

     

    왜 만들었나?

    Flash Player 10이 나오면서 기존 Javascript-Flash 기반 다중 파일 업로더 기능이 제대로 동작하지 않아 이것을 쓰는 많은 사이트들이 발등에 불떨어지듯한 상황이 발생하게 되었다. 내가 참여하고 있는 스타플(http://starpl.com)도 예외는 아니다.

     

    원인을 살펴보자면 Flash Player의 파일 업로드에 대한 보안(?)정책이 바뀐 것에 기인한다. Ajax(Javascript)만 가지고 실시간 다중 파일 업로드가 안되기 때문에 Ajax에서 직접 ExternalInterface로 Flash의 FileReference.browse() 메소드를 호출하여 파일을 업로드하고 그 결과를 다시 Ajax로 ExternalInterface를 이용해 반환하는 거였는데 여기서 문제될 만한 부분은 Ajax쪽에서 사용자 조작이 있어 browse() 메소드를 호출하면 바로 “Error #2176 팝업 창 표시와 같은 특정 동작은 마우스를 클릭하거나 버튼을 누르는 것과 같이 사용자가 조작하는 경우에만 발생합니다.”라는 에러가 발생한다. 즉, Flash Player 10부터는 Flash 내부가 아닌 외부 Ajax와 같은 사용자 조작으로는 browse() 메소드를 호출할 수 없다!

     

    Flash Player 10 부터는 Flash에 버튼을 만들고 FileReference.browse()를 호출해야한다.

     

    왜 공개했나?

    소스 공개의 묘미는 함께 알아가는 가는 거다. 본인의 실력이 특출나기 때문이 아니라는 점을 강조하고 싶다. 오히려 부족함을 느끼기 때문에 블로깅을 하는 것과 동일하다. 블로깅을 하면 나도 모르게 몰랐던 것도 알아간다. 왜냐하면 블로깅을 통해 관심 분야의 사람들과 만날 수 있기 때문이다. 소스 공개도 마찬가지이다. 새로운 이슈에 대해 나만 알고 있다면 얼마나 이기적인가? 어짜피 알거면 같이 알아가면서 함께 발전하자는게 나의 생각이다.

     

    그런 의미에서 여러분도 with 블로깅&소스공개?!

     

    멀티 파일 업로더 (Multi - file uploader) 에 대해

    사용하는데는 어려움이 많이 없을거라 생각합니다.


    이미 swfuploader라는 좋은 툴이 있긴 하지만 학습과 쉬운 소스 수정을 위해 직접 만들었습니다.
    공유해서 함께 지식을 넓혀가길 희망합니다.


    이름은 Multi-file uploader이지만 실제 동작은 1개씩 업로드 되는 겁니다.

     

    * author : 지용호 (Yongho, Ji)

    * Q&A : jidolstar[at]gmail.com, http://blog.jidolstar.com)

    * license : LGPL (수정시에는 소스를 공개합니다. 하지만 그대로 사용하는 것은 사용 출처만 밝혀주세요.)

    * 최초제작일 : 2008.10.24

    * 최종수정일 : 2009.03.02

    * 제작언어 : Adobe ActionScript 3.0

    * 제작환경 : Adobe Flex Builder 3 Professional. Flex SDK 3.2

    * 구동환경 테스트 : IE6, FF, google chrome

    * 제작배경
      Flash Player 10이 정식 릴리즈 됨에 따라 Javascript를 통해 FileReference.browse() 메소드를 호출을 방지하도록 되었기 때문에 이 방식을 사용한 것을 대체하려고…

     

    * 첨부파일 설명

    1. FAService는 Javascript-SWF간 통신하기 위한 라이브러리이다.
    2. fileupload는 멀티파일업로더 핵심 라이브러리이다.
    3. fileuploader는 fileupload와 FAService를 이용해서 HTML환경에서 멀티파일업로드를 가능하게 만들어진 애플리케이션이다.
    4. fileuploader/html-template/index.template.html 부분처럼 사용하면 되겠다.
    5. fileuploader/php 에는 예제로 만들어진 php소스가 있다. jsp, asp로 비슷하게 만들어 쓰면 되겠다.

    * 추가사항

    •  2008.11.14
           1. POST, GET 방식으로 Variables를 넘길 수 있도록 함
           2. requestHeaders를 추가할 수 있도록 함 (헤더를 보내는 경우 서버측 crossdomain.xml에 allow-http-request-headers-from 설정이 되어야 한다.)
           3. contentType(MINE Type)을 지정할 수 있도록 함
           이러한 방식은 AS3의 URLRequest에 있는 속성이므로 참고하길 바란다.
           사용예는  index.template.html의 flashvars 참고하면 된다.
    •  2009.03.02
           banfileExtensions 추가. flashvars에 추가된 속성으로 업로드를 거절할 확장자를 가진 파일을 등록한다. 대소문자는 자동으로 맞춰준다. 사용자가 업로드 금지 파일을 등록하려는 경우 “fileuploader_banFileExtension” 이벤트가 발생한다. 자세한 사용법은 index.template.html을 참고한다.  ex) banfileExtensions = “mp3;php;pl;”;

    * 사용방법

    1. 자바스크립트를 통해 이벤트 핸들러를 등록한다.(FAService Flex-Ajax 통신 브릿지 이용, Flex-JS로 제작됨 , 본인 제작)
    2. 파일 업로드를 위한 설정 Flash Vars로 등록 한다.(가령 업로드할 서버 경로, 파일 사이즈, 필터, 버튼이미지 경로등…)
    3. 업로더 SWF를 HTML상에 붙인다. 예제에서는 swfobject.js를 이용했다.
    4. 서버쪽 프로그램을 만든다. 첨부된 php파일을 참고하면 되겠다. asp, jsp든 어떤 언어를 써도 동일하게 만들면 되겠다.
    5. 예제에선 정상적으로 동작하는 경우 textarea에 ready가 뜬다. 이벤트 핸들러가 호출되면 여기에 출력하도록 짜여졌다.
    6. 파일 선택후 ok하면 이벤트는 fileuploader_startAll, (fileuploader_start, fileuploader_step, fileuploader_end), fileuploader_endAll 순으로 진행된다. 중간에 ()안에 들어간것은 여러개의 파일의 경우에 진행상황에 따라서 번갈아가며 호출된다.
    7. 서버 접속이 원할치 않는다면  fileuploader_fail 이벤트가 발생한다.(보안 또는 IO Error)
    8. 파일 선택을 취소하면 fileuploader_cancel가 발생한다.
    9. 1개의 파일 사이즈가 정해진 크기보다 크면 fileuploader_fileSizeError 이벤트가 발생한다.
    10. . 선택한 파일의 갯수가 정해진 갯수보다 크면 fileuploader_fileCountError 이벤트가 발생한다.
    11. . 중간에 uploaderFAService.call( “stop”, null )을 호출하게 되면 업로드가 최소되고 fileuploader_stopAll 이벤트가 발생한다.
    12. 금지된 파일을 선택한 경우 fileuploader_banFileExtension 이벤트가 발생한다.

    * 이벤트

    이벤트 발생시 파라미터들은 JSON Object 형태이다.

    • ready  없음
    • fileuploader_startAll : {”totalCount”:total count of files, “totalSize”:total size of files(bytes) }
    • fileuploader_start : {”filename”:file name, “bytesTotal”:size of file(bytes)}
    • fileuploader_step : {”filename”:file name, “bytesTotal”:size of file(bytes), “bytesLoaded”:uploaded size of file(bytes)}
    • fileuploader_end : {”filename”:file name, “bytesTotal”:size of file(bytes), “uploadCompleteData”:…}  여기서 uploadCompleteData는 서버 개발자가 마음대로 값을 바꿀 수 있다. JSON 형태의 String값으로 넘겨주면 프로그램에서는 자동적으로 Object형태로 반환해준다.
    • fileuploader_fail : {”filename”:file name, “bytesTotal”:size of file(bytes, “msg”:error message}
    • fileuploader_endAll : {”failCount”: count of files upload failed, “endCount”: count of files upload successed , “totalCount”: count of files tried to upload }
    • fileuploader_stopAll : {”totalCount”:total count of files, “totalSize”:total size of files(bytes) }
    • fileuploader_fileSizeError : {”filename”:file name, “bytesTotal”:size of file(bytes), “maxFileSize”: maximum file size(bytes)}
    • fileuploader_fileCountError :{”totalCount”:total count of files, “maxFileCount”: maximum count of files }
    • fileuploader_banFileExtension : {”filename”:filename, “banFileExtension”: extension of selected file, “banFileExtensions”: registered ban extensions}

     

    * 함수

    • uploaderFAService.call(’browse’,null );  Flash Player 9이하일때는 다음과 같은 방법으로 파일 browsing을 요청할 수 있다. 하지만 이 방법은 사용을 권장하지 않는다.

     

    * FAService에 대해

    1. FAService는 Flex-Ajax 통신 라이브러리이다.(ActionScript 3 프로젝트로도 사용이 가능)
    2. 여기서는 소스도 함께 공개했다.
    3. FABridge와 비교할때 최소기능만 사용하도록 만들었다.
    4. 단순히 addEventListener, removeEventListener, call 만으로 Flex와 Ajax간에 통신합니다.
    5. addEventListener은 Flex에서 발생하는 이벤트명, JS이벤트 함수, 우선순위 값이 들어갑니다. 우선순위 값은 같은 이벤트 발생시 호출한 JS이벤트핸들러 함수의 호출 순서를 정합니다. 숫자가 클수록 등록순에 관계없이 먼저 호출된다.
    6. JS 이벤트 핸들러 함수의 파라미터 값들은 Flex에서 정해서 보내줍니다. Object형이 일반적이지만 Array, Boolean, int, float,String 형등 다양한 형태가 될 수 있다.
    7. removeEventListener은 기존에 등록한 이벤트 핸들러를 삭제해준다.
    8. call 함수는 Flex쪽에 “호출명”이 등록되어 있어야만 호출된다. 인수값은 Object, Array, Boolean, int, float,String 형등이 모두 가능하며 이 값은 Flex쪽에서 정한다.
    9. 만약 Flex에서 정한 형태로 만들어지지 않으면 JS Alert 창을 띄우게 된다.
    10. call 함수는 반드시 ready 이벤트가 발생한 시점 이후로 사용해야한다. 이전에는 적용되지 않습니다.(스텍을 이용해서 명령을 저장해두었다가 하는 방법도 모색하고 있음, addEventHandler는 그렇게 하고 있음)
    11. 등록되어진 이벤트 핸들러, 호출가능한 call 함수 목록등을 반환할 수 있는 함수를 만들 필요가 있다고 생각한다.

     

    소스 다운로드

     

    읽어볼만한 글

    Adobe Flex를 접할때 어려워하는 부분중에 하나가 Event입니다.
    깊숙히 이해하려고 할때 잘 이해가 안되는 부분이 많은게 이벤트죠.

    기본에 충실해야한다!


    프로젝트(http://starpl.com)를 하면서 많이 느꼈습니다.


    처음 Flex 접했을때 가장 어려움을 겪었던 것은 기본이 탄탄하지 못한 것으로부터 였습니다.


    그래서 Flex와 함께 ActionScript 3.0의 기본 문법 및 대략적 내용을 이해해야한다고 생각합니다.


    단순한 문법뿐 아니라 분석/설계에도 관심을 가지고 공부해서 명확하고 깨끗한 프로그램을 만들 수 있도록 노력해야겠구요.

     

    아래는 Event에 대한 질문을 걸어놓고 스스로 답변까지 해두었습니다.
    이것으로 Event 전부를 설명할 수 없지만 몇가지 궁금한 사항을 이해하는데는 도움이 될겁니다.


    그냥 읽고 지식만 얻어가지 마시고 없는 질문이 있다면 스스로 질문/답변을 댓글로 달아주시면 더욱 고맙겠습니다. 잘못된 내용이나 추가해야한다는 것도 포함합니다.

     

    1. ActionScript 3.0 이벤트(Event)

    1.1 ActionScript 2.0과 3.0에서 이벤트가 바뀐점은?

    • ActionScript 2.0에서는 이벤트 리스너를 추가하는 경우 상황에 따라 addListener() 또는 addEventListener()를 사용하지만 ActionScript 3.0에서는 항상 addEventListener()를 사용합니다.
    • ActionScript 2.0에는 이벤트 흐름이 없으므로 이벤트를 브로드캐스팅하는 객체에서만 addListener() 메서드를 호출할 수 있지만 ActionScript 3.0에서는 이벤트 흐름에 포함된 모든 객체에서 addEventListener() 메서드를 호출할 수 있습니다.
    • ActionScript 2.0에서는 함수, 메서드 또는 객체를 이벤트 리스너로 사용할 수 있지만 ActionScript 3.0에서는 함수 또는 메서드만 이벤트 리스너로 사용할 수 있습니다.
    • on(event) 구문은 이제 ActionScript 3.0에서 지원되지 않으므로 ActionScript 이벤트 코드를 무비 클립에 첨부할 수 없습니다. 이벤트 리스너를 추가할 때는 addEventListener()만 사용할 수 있습니다.

    1.2 Event를 사용해야하는 이유? 2가지 이상

    • 객체간 강한 결합 해결 : 애플리케이션 설계시 Callback함수를 사용하는 등의 강한결합을 이용하는 경우 유지보수 및 확장이 어려워지는 경우가 크다. 이벤트는 객체간 느슨한 결합을 해주는데 큰 힘을 발휘할 수 있다.
    • 작업에 대한 일관성 없는 응답 : 동기적 방식과 비동기적 방식에 대한 응답에 대한 처리를 이벤트를 사용하지 않는 경우 서로 다른 방법을 선택해서 사용해야한다. 이벤트는 1가지 방법으로 응답처리를 가능하게 해준다.


    2. Event의 구성요소 3가지는 무엇이고 의미를 설명하라.

    • 이벤트 객체(Event Object)
      flash.net.Event의 객체나 이를 확장한 이벤트 객체를 의미한다. 이벤트 객체에는 이벤트에 대한 정보가 들어가있다. 이벤트의 종류(Type)과 이벤트를 발생한 객체(target)에 대한 정보가 포함된다.
    • 이벤트 발생자(Event Dispatcher)
      이벤트를 발생(broadcast)하는 객체이다. 이벤트 객체에 target으로 이벤트 발생자 정보가 담긴다. flash.net.EventDispatcher를 확장한 모든 객체는 이벤트 발생자가 될 수 있다. EventDispatcher의 dispatchEvent() 메소드에 이벤트 객체를 담아 실행하면 이벤트가 발생하게 된다.
    • 이벤트 청취자(Event Listener)
      일종의 메소드 또는 함수이다. 발생한 이벤트를 듣는 역할을 한다. EventDispatcher클래스를 확장한 클래스의 객체에서 addEventListener()을 이용해 이벤트 청취자를 등록할 수 있다. addEventListener()을 이용하면 청취할 이벤트의 종류(type)을 결정할 수 있으며 청취할 이벤트 흐름(capture,target,bubbling) 및 우선순위등을 선택할 수 있다.

    3. Event Listener를 등록/삭제하는 방법을 기술하라.

    • 등록
      EventDispatcher의 addEventListener() 메소드를 이용한다.
      예)
      var button:Button = new Button();
      button.addEventListener( MouseEvent.CLICK, onClick, false );
      public function onClick( event:MouseEvent )
      {
        trace( event.type, event.target  );
      }
    • 삭제
      EventDispathcer의 removeEventListener() 메소드를 이용한다.
      예)
      button.removeEventListener( MouseEvent.CLICK, onClick, false );
      3개의 인자가 addEventListener()를 사용할때와 같아야 정상적으로 이벤트 청취자 등록이 삭제된다.
      참고로 맨 마지막 인자는 useCapture이다.

    4. Event 전파단계의 종류 3가지가 무엇이며 각각에 대한 설명

    • 이벤트 전파단계의 종류 : 캡쳐(capture) 단계, 타겟(target) 단계, 버블(bubble)단계
    • 이벤트 전파 순서 : 캡쳐->타겟->버블
    • 캡쳐 단계
      루트(root) 디스플레이 객체서부터 이벤트를 발생한 객체(target)까지 디스플레이 객체 리스트(display Object List)를 따라 검색해나가는 단계이다. 즉, 부모->자식으로 이벤트를 전파한다. 이 캡쳐 단계에 이벤트를 청취하기 위해서는 addEventListener()의 3번째 인자인 useCapture가 반드시 true로 되어야 한다. false로 되어 있다면 캡쳐단계가 아닌 타겟과 버블 단계에서 이벤트 청취를 한다.
      캡쳐단계 청취하는 목적은 다양할 수 있다. 가령 중간에 이벤트 전파를 중지시킬때 필요할 수 있다. 이해가 안될 수 있지만 실제 프로젝트를 하면 이런 경우가 다분하다.
      사용 예 )
      button.addEventListener( MouseEvent.CLICK, onClick, true );
    • 타겟 단계
      갭쳐 단계 이후 타겟단계에 도달한다. 타겟 단계는 이벤트를 발생시킨 객체까지 도달했을때 이뤄진다.
    • 버블 단계
      타겟 단계 이후 다시 루트(root) 디스플레이 객체로 이벤트가 전파되는 단계이다. 버블버블 물망울이 수면위로 다시 올라가는 것을 연상하면 되겠다. 캡쳐와 반대로 자식->부모로 이벤트가 전파되는 단계이다.
      이벤트가 버블 단계로 전파되기 위한 조건은 이벤트 송출시 Event의 생성자 인자값중 bubble값을 true로 설정되어 있어야 한다. MouseEvent같은 경우 이 bubble값이 true로 설정되어 있다.
      타겟단계와 버블단계에 이벤트 청취자를 등록하기 위해 addEventListener()의 3번째 인자 useCapture가 false로 되어야 한다. useCapture를 설정하지 않으면 기본값이 false이다.
    • 이벤트 객체가 전파되는 기능이 굳이 필요한 이유
      부모-자식1-자식2….-자식100 의 형태로 디스플레이 객체 리스트가 형성되어있다고 가정하자 자식50에서 자식 100을 마우스 클릭했을때 이벤트를 받고 싶다고 하자. 자식50은 자식100의 존재유무를 모른다(알 수 있지만 알게되면 강한결합이 일어난다.) 이런 경우 이벤트 객체가 전파되는 기능은 매우 유용하게 쓰일 수 있다. 캡쳐나 버블 단계에서 자식50은 자식100에서 발생된 이벤트를 받을 수 있다.
    • flash.display.DisplayObject 클래스는 EventDispatcher를 확장해서 만들어졌다는 것에 주목하자.

    5. 4번의 Event 전파는 어떤 환경에서 가능한가?

    • 반드시 디스플레이 객체 리스트(Display Object List) 상에 존재하는 디스플레이 객체(Display Object)여야 이벤트 전파가 가능하다. 그 외의 이벤트 전파는 타겟 단계밖에 없다. 가령, HTTPService나 URLLoader에서 이벤트가 발생하는 경우 이들은 디스플레이 객체가 아니다.
    • 이벤트 전파의 조건
      1. 디스플레이 객체여야 한다.(flash.display.DisplayObject를 확장한 클래스의 객체)
      2. 디스플레이 객체는 디스플레이 객체 리스트에 존재해야 한다. 즉, stage로 부터 addChild되어 있어야 한다.

    6. 이벤트 전파시 target과 currentTarget의 차이점은 무엇인가? 예를 들어 설명하라.

    • target은 이벤트를 발생한 객체이고 currentTarget은 이벤트를 청취하는 객체이다.
    • 예시)
      var p:Sprite = new Sprite(); //parent
      var c:Sprite = new Sprite(); //child
      addChild( p );
      p.addChild( c ); //c가 p의 자식인 것에 주목
      p.addEventListener( MouseEvent.CLICK, onClick ); //이벤트 청취를 p가 하는 것에 주목!
      public function onClick( event:MouseEvent )
      {
      trace( event.target, event.currentTarget ); //c를 클릭했을 경우 c, p가 나오고 p를 클릭했을 경우 p,p가 나온다.
      }

    7. Event 우선순위에 대해서 간단한 예제를 들어 설명하라.

    • 이벤트 우선순위는 구체적으로 이벤트 청취 우선순위라고 할 수 있다.
    • 이벤트 청취 우선순위는 addEventListener()의 4번째 인자인 priority를 이용한다. 이 값이 클 수록 청취 우선순위가 높아지며 addEventListener()의 등록 순서에 상관없이 우선순위에 따라 이벤트 리스너가 호출된다.
    • 이벤트 청취 우선순위가 유효한 조건
      1. 같은 객체에서 청취한다.
      2. 같은 이벤트 종류(type)을 청취한다.
      3. 같은 useCapure 값을 가진다.
    • ex)
      a.addEventListener(MouseEvent.CLICK, aHandler, false, 3);
      a.addEventListener(MouseEvent.CLICK, bHandler, false, 1);
      a.addEventListener(MouseEvent.CLICK, cHandler, false, 100);

      a객체에 MouseEvent.CLICK 이벤트가 발생시 등록순서에 상관없이 priority값이 가장큰 cHandler가 먼저 호출되고 다음으로 aHandler, bHandler가 호출된다.

    8. Event 전파를 중단하는 메소드 2개가 무엇이며 간단한 예제를 들어 각각의 차이점을 설명하라.

    • Event 클래스의 stopPropogation()와 stopImmediatePropogation() 메소드이다.
    • 차이점
      - stopPropogation()
        이벤트 전파를 중단하되 같은 노드의 이벤트를 청취까지는 허락한다.
      - stopImmediatePropogation()
        이벤트 전파를 즉시 중단한다.
    • 예제
      back이 배경 canvas이고 button1이 back에 포함된 button일때
      back.addEventListener(MouseEvent.CLICK, event1Handler);
      back.button.addEventListener(MouseEvent.CLICK, event2Handler);
      back.button.addEventListener(MouseEvent.CLICK, event3Handler);
      back.button.addEventListener(MouseEvent.MOUSE_OUT, outHandler);private function event1Handler(e:MouseEvent):void
      {
             trace(’back’);
      }
      private function event2Handler(e:MouseEvent):void
      {
             trace(’button’);
             e.stopPropagation();
             e.stopImmediatePropagation();
      }
      private function event3Handler(e:MouseEvent):void
      {
             trace(’button2′);
      }
      private function event1Handler(e:MouseEvent):void
      {
             trace(’out’);
      }

      e.stopPropagation(); e.stopImmediatePropagation(); 두가지 다 없을경우
              button
              button2
              back
              out
      e.stopPropagation(); 만 실행
              button
              button2
              out
      e.stopImmediatePropagation(); 만 실행
              button
              out

    9. addEventListener의  useCapture 인자는 무엇을 의미하는가? 간단한 예제를 들어 설명하라.

    • 이벤트 전파를 청취할때 캡처단계를 청취할지 타겟 및 버블 단계를 청취할지 결정하는 인자이다.
    • 모든 전파 단계에서 청취하기 위해서는 useCapture가 다른 2개의 addEventListener()를 사용해야한다.
    • 예제 생략

    10. Sprite, Shape와 같은 클래스에서 어떻게 하면 이벤트를 송출할 수 있는가?

    • dispatchEvent() 메소드를 호출하면 된다.

    11. Sprite, Shape와 같이 이벤트 송출할 수 있는 클래스는 근본적으로 어떤 클래스를 상속받았기 때문에 가능한가?

    • Sprite와 Shape는 궁극적으로 DisplayObject를 확장해서 만들어졌으며 DisplayObject는 EventDispatcher를 확장했다. 그러므로 Sprite와 Shape와 같은 클래스는 EventDispatcher의 메소드를 전부 사용할 수있다. 이중에 dispatchEvent()도 사용할 수 있다.

    12. IEventDispatcher는 무엇이며 어떤 경우에 이것을 사용해야할까?

    • EventDispatcher의 인터페이스이다.
    • IEventDispatcher를 활용하는 것은 다중상속이 불가능한 AS3에서 유용하다.
      가령, 어떤 클래스가 ProxyObject를 확장해서 만들어져야한다. 그런데 이 클래스는 이벤트도 발생시켜야 하므로 EventDispatcher클래스도 확장해야한다. ActionScript 3.0은 다중상속을 지원하지 않는다.(다중상속은 이점도 있지만 단점도 있다.) 그러므로 이러한 경우에는 주가 되는 클래스인 ProxyObject를 is-a 확장을 하고 EventDispatcher를 has-a 확장을 감행한다음 만들어진 클래스가 EventDispatcher의 기능을 수행한다는 것을 명시적으로 외부에 알려주고 내부적으로 구현시켜주기 위해 IEventDispatcher 인터페이스를 implements 해준다. 이렇게 하면 원하는 기능인 ProxyObject와 EventDispatcher의 기능을 전부 수행하면서도 다중상속의 문제를 극복할 수 있다.
    • ex)
      public class MyProxyObject extends ProxyObject implements IEventDispatcher
      {
        var evnetDispatcher:EventDispatcher = new EventDispatcher();
       
        //내부적으로 IEventDispatcher에 선언한 메소드를 구현한다.  가령…
        public function dispatchEvent( event:Event ):Boolean
        {
           return  eventDispatcher.dispatchEvent( event );
        }
      }

    13. preventDefault()와 stopPropagation(), stopImmediatePropagation()의 근본적 차이점은 무엇인가?


    • preventDefault()는 기본 동작(dafault behaviors)를 취소하는데 쓰이는 메소드인 반면 stopPropagation(), stopImmediatePropagation()는 이벤트 전파에만 영향을 준다.

     

    • preventDefault()가 사용 가능한지는 isDefaultPrevented() 메소드로 알 수 있다.

    14. preventDefault()를 호출해서 실행을 가능하게 하기 위한 조건은 무엇인가?

    • 이벤트 객체를 생성시 cancelable속성이 true로 설정되어 있어야 한다.

    15. 커스텀 이벤트를 만들어야하는 어떤 경우이며 어떻게 만드는가?

    • 커스텀 이벤트는 기본 이벤트 클래스(flash.events.Event) 또는 그 상위 이벤트 클래스를 확장하는 경우를 의미한다.
    • 이벤트를 확장해야하는 경우는 이벤트 송출시에 필요한 정보를 담아내기 위함이다.
    • 만드는 방법
      class MyEvent extends Event
      {
        public static const MY_EVENT:String = “myevent”;
        public var value:String;
        public function MyEvent( type:String, value:String ):void
        {
           super( type, false, false );
           this.value = value;
        }
        overide public function clone():Event
        {
          return new MyEvent( type, value );
        }
      }

    16. 커스텀 이벤트가 bubbling이 가능하기 위해 어떤 조건이 필요한가?

    • Event의 생성자의 2번째 인자인 bubble값을 true로 설정한다.

    17. 커스텀 컴포넌트 만들때 clone()함수를 override해야하는 이유는 무엇인가?

    • clone()메소드는 기존과 신규 properties들을 clone 내로 설정해 이벤트 객체의 복사본을 돌려준다.
      clone() 메소드를 Event 클래스 확장시 오버라이드 해야하는 이유는 이벤트를 다시 재전달해야하는 경우 묵시적으로 복사해서 재전달할 필요가 있을때 필요하기 때문이다.

     

    • 참고로 toString()도 확장해서 사용하는 것이 좋다. 이벤트에 대한 명세서가 될 수 있기 때문이다.

    18. 약참조에 대해서 GC(가비지 콜렉션)과 관련되어 설명하라.

    • addEventListener()함수로 이벤트 청취자를 등록할때 5번째 인자로 useWeakReference 인자가 있다. 이것을 true로 설정하면 이벤트 청취자가 이벤트 청취를 약참조를 하겠다는 의미이다. 반대로 false(기본)이면 강참조이다.
    • 약참조, 강참조를 설명하기 전에 가비지 콜렉션(GC, Garbage Collection)에 대해서 먼저 알아야한다.
      만들어졌던 객체가 더이상 쓸모가 없어졌을때 GC라고 한다. 쓸모가 없어졌다는 것은 참조의 고리가 끊어졌다는 것을 의미한다. 메모리에 상주해 있지만 조만간 메모리에서 없애야할 대상인 것이다. Flash Player는 GC로 선정된 객체들을 메모리에서 삭제하는 시점을 내부적으로 구현해놓고 있다.(많은 분들이 Flash Player GC처리를 못한다고 하지만 실제로는 문제없이 잘하고 있다. 단지 효율적으로 GC처리를 하는지는 의문이지만…)
      Flash Player GC 대상을 찾아내기 위한 방법은 2가지가 있다. 그것은 레퍼런스 카운팅(reference counting)과 마크 앤 스윕(Mark and Sweep)이다. 레퍼런스 카운팅은 생성된 객체가 참조할때마다 참조 카운트를 1씩 더해준다. 또 참조에서 끊어지는 경우 1씩 감소시켜준다. 만약 레퍼런스 카운팅 값이 0이되는 경우 바로 GC 대상이 되는 것이다. 마크 앤 스윕은 레퍼런스 카운팅 만으로 찾을 수 없는 GC대상을 찾아준다. 가령, 두개 이상의 객체가 서로 다른 객체를 참조하고 있다고 가정한다.(순환참조) 그럼 이들 레퍼런스 카운팅 값은 1 이상이다. 그러므로 레퍼런스 카운팅 방법으로는 GC대상을 만들 수 없다. 마크 앤 스윕은  이들 객체가 최상위 부모와 연결되어 있는지 검사하는 방법이다. 부모로 부터 꼬리에 꼬리를 물고 객체가 참조되어 있는 것인지 조사해나가는 방법으로 GC대상을 검색한다.
      레퍼런스 카운팅과 마크앤 스윕 방법으로 찾아낸 GC대상은 메모리에 상주되지만 Flash Player의 GC 삭제방법 로직에 의해 어느 순간 삭제된다.(이 시점은 프로그래머가 어찌할 수 없다. AIR 또는 Flash Player Debug버전의 경우에만 System.gc()를 이용해 강제적으로 GC를 메모리에서 삭제할 수 있긴 하다.)
    • addEventListener()의 useWeakReference를 true로 설정하면 약참조가 되어 레퍼런스 카운팅을 증가시키지 않는다. 강참조의 경우 이벤트 대상(target)의 참조를 지웠다고 해도 addEventListener로 등록한 청취자 메소드가 GC대상이 아니라면 이벤트 대상의 레퍼런스 카운팅 값은 0이 될 수 없다. 결국 프로그래머의 뜻과 다르게 메모리를 낭비하는 일을 초래할 수 있다. 물론 이벤트 대상이 사라지는 시점에 removeEventListener() 사용하면 되겠지만 때로는 그 시점을 모르는 경우도 발생한다. 이러한 경우 useWeakReference를 true로 설정해서 메모리 낭비를 없앨 수 있겠다.
    • Flex에서 List 계열의 컴포넌트를 살펴보면 dataProvider()에 ArrayCollection 객체를 받을때 ArrayCollection의 변화를 살펴보기 위해 addEventListener()를 사용한다. 그런데 ArrayCollection 객체는 언제 어떻게 될지 List 계열 컴포넌트는 알 수 없을 것이다. 이때 유용하게 사용하는 것이 바로 useWeakReference=true로 설정해서 필요없어진 ArrayCollection이 메모리 남는 것을 방지할 수 있다. 참고 : http://blog.jidolstar.com/375


    19. 이벤트가 전파되는 중간에 전파를 멈추게 하기 위해 stopPropagation()을 사용하지 않고removeEventListener() 를 사용하면 안되는 이유를 설명하라. (힌트, ActionScript 3.0의 Native 메커니즘에 입각한다. http://bixworld.egloos.com/2149717)

     

    • 이벤트 송출을 시작하면 flash player는 이벤트가 송출될 디스플레이 객체 리스트를 전부 조사해 미리 이벤트 객체를 복사해두기 때문이다. 이미 전파할 대상이 모두 정해져있고 전파가 시행중인 경우에는 중간에 removeEventLisener를 사용하더라도 일단 송출된 이벤트는 끝까지 전파된다. 다음 시점 이벤트 송출시에는 removeEventLisener()가 적용되어 있는 상태가 된다.
     

     

     

    20. willTrigger()와 hasEventListener()의 의미를 설명하고와 두 메소드의 차이점을 보여줄 수 있는 대한 예를 들어보자.

     

     

     

    • 공통점
      hasEventListener()이나 willTrigger() 모두 이벤트 청취자가 있는지 검사한다.
    • 차이점
      hasEventListener() : 지정한 객체에(만) 이벤트 청취자가 있는지 조사한다. 이벤트 흐름과는 무관하다.
      willTrigger() : 지정한 객체를 포함하는 디스플레이 객체 리스트에 해당 이벤트가 청취자가 있는지 조사한다. 그러므로 이벤트 흐름과 관련 있다.
    • 예제
      var button:Sprite = new Sprite();
      addChild( button );
      this.addEventListener( MouseEvent.CLICK, onClick );
      trace( button.willTrigger( MouseEvent.CLICK ) ); //true
      trace( button.hasEventListener( MouseEvent.CLICK ) ); //false ->왜 false가 되는가 아는게 중요!
      trace( this.willTrigger( MouseEvent.CLICK ) ); //true
      trace( this.hasEventListener( MouseEvent.CLICK ) ); //true
    • 이 두개의 함수를 잘 활용하면 쓸데없이 이벤트를 송출하는 경우를 방지할 수 있다.

    21. ActionScript 3.0 이벤트에 대해 참고할 만한 문헌 및 자료를 조사하자.

    Douglas9님의 블로그에 소개된 ActionScript Cookie Util을 보고 포스팅한다.

     

    ActionScript 3.0만으로 쿠키를 제어할 수 있다? 그냥 막연하게 할 수 있다고 생각했는데 소개할 유틸 코드와 같은 방식을 생각하지 않았다. 본인은  HTML안에 작성된 Javascript를 코드를 사용하지 않고 ActionScript 3.0만으로 쿠키를 제어할 수 있다는 생각을 못했다. 근데 그게 가능했다. (왜 진작 이 생각을 못했지?)

     

    소개할 유틸의 사용하는 방법은 다음과 같이 아주 간단하다.


    import de.aggro.utils.CookieUtil;
    //Set a cookie named mycookie with a value of mycookie value with a time to live of 30 days
    CookieUtil.setCookie(“mycookie”, “mycookie value”, 30);
    //Get that cookie and trace its value
    trace(CookieUtil.getCookie(“mycookie”));
    //Delete the cookie from the users computer
    CookieUtil.deleteCookie(“mycookie”);

     

    더 이상 설명할 필요가 없을 정도로 쉽게 사용할 수 있다.  아래는 유틸 소스이다.


    package de.aggro.utils
    {
        import flash.external.ExternalInterface;
       
        public class CookieUtil
        {
            public function CookieUtil()
            {
            }
           
            private static const FUNCTION_SETCOOKIE:String =
    “document.insertScript = function ()” +
    “{ ” +
    “if (document.snw_setCookie==null)” +
    “{” +
    “snw_setCookie = function (name, value, days)” +
    “{” +
    “if (days) {”+
                                “var date = new Date();”+
                                “date.setTime(date.getTime()+(days*24*60*60*1000));”+
                                “var expires = ‘; expires=’+date.toGMTString();”+
                            “}” +
                            “else var expires = ”;”+
                            “document.cookie = name+’='+value+expires+’; path=/’;” +
             “}” +
    “}” +
    “}”;
           
            private static const FUNCTION_GETCOOKIE:String =
    “document.insertScript = function ()” +
    “{ ” +
    “if (document.snw_getCookie==null)” +
    “{” +
    “snw_getCookie = function (name)” +
    “{” +
    “var nameEQ = name + ‘=’;”+
                            “var ca = document.cookie.split(’;');”+
                            “for(var i=0;i < ca.length;i++) {”+
                                “var c = ca[i];”+
                                “while (c.charAt(0)==’ ‘) c = c.substring(1,c.length);”+
                                “if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);”+
                            “}”+
                            “return null;” +
             “}” +
    “}” +
    “}”;

    private static var INITIALIZED:Boolean = false;
           
            private static function init():void{
                ExternalInterface.call(FUNCTION_GETCOOKIE);
                ExternalInterface.call(FUNCTION_SETCOOKIE);
                INITIALIZED = true;
            }
           
            public static function setCookie(name:String, value:Object, days:int):void{
                if(!INITIALIZED)
                    init();
               
                ExternalInterface.call(“snw_setCookie”, name, value, days);
            }
           
            public static function getCookie(name:String):Object{
                if(!INITIALIZED)
                    init();
               
                return ExternalInterface.call(“snw_getCookie”, name);
            }
           
            public static function deleteCookie(name:String):void{
                if(!INITIALIZED)
                    init();
               
                ExternalInterface.call(“snw_setCookie”, name, “”, -1);
            }

        }
    }

    소스를 보니 놀랍다. ExternalInterface를 이용해 JavaScript와 통신할 수 있다는 것은 알고 있을 것이다.  초반에 ExternalInterface의 call()메소드를 이용해 쿠키를 제어할 함수를 Javascript의 document.insertScript로 등록한 뒤, 등록된 함수를 사용하는 형태이다. 즉, 자바스크립트 코드를 심어주고 그것을 호출하는 형태이다. 이러한 방법은 쿠키를 제어하는 것 뿐아니라 다양한 방법으로 응용될 수 있다고 생각한다.

     

    소개한 유틸은 순수 ActionScript 3.0에서 만들었기 때문에 Flash, Flex 등 상관없이 유용하게 사용할 수 있겠다.  아래링크에 만든사람의 블로그와 위 코드를 다운로드 받을 수 있는 링크를 걸어두었다. 참고하자.

    이런식으로 만든 IFrame 이 있는데 아래에서 볼 수 있다.

    웹서핑중에 재미있는 사이트가 있어서 소개한다.

     

    사이트 이름은 wonderfl이다. 부제로 build flash online 로 되어 있다.


    부제만 보면 알 수 있듯이 사이트는 ActionScript 3.0 코드를 온라인상에 입력하면 이 코드를 서버에서 컴파일해주고 바로 그 결과를 보여주는 사이트이다.

     

    이름도 참 독특하다. wonderful을 보방해서 wonderfl로 했다.  뒤에 fl은 Adobe Flash의 Fl인듯 싶다.

     

    이런 컨셉과 비슷한 사이트는 몇번 본 것 같은데 ActionScript 3.0을 컴파일해서 서비스하는 것은 처음본다.


    서버에서는 Flex의 mxmlc를 이용해 컴파일하는 것으로 보인다.

    각종 filterbitmap 처리를 통한 Effect, pixel manipulation, pixel bender 그리고 papervision3D등의 예제가 여러개 올라와 있다.

     

    본인도 가입해서 한번 만들어보았다. Papervision3D가 지원되기 때문에 그에 알맞은 코드를 찾아다가 아래 그림과 같이 코딩해보았다. 훌륭하다. ^^ 또한 Flash Player 10 기반으로도 만들 수 있기 때문에 Matrix3D, Vector3D, Vector와 같은 클래스나 연산자를 사용하여 코드를 테스트 해볼 수 있다.

     

    샘플코드는 Clockmaker 블로그에서 얻어왔다.

     

    아쉬운 점은 블로그에 결과물을 붙힐 수 없다는 것이다(적어도 내 블로그에는…). 하지만 링크를 통해 만든 결과물을 보여줄 수 있다. 아래 링크로 확인하기 바란다.

    이 사이트가 어떤 사업성을 가질지는 의문(그냥 개인이 심심해서 만든 것 같음)이지만 매우 재미있는 있고 독특한 사이트라고 느꼈다.

    Prototype 디자인 패턴GoF의 23가지 디자인 패턴중 생성관점에서 바라보는 패턴중에 하나이다. 생성관점에서 바라보는 이유는 복사를 해서 새로운 객체(인스턴스,instance)를 만든다는 개념을 가지고 있기 때문이다.

    Prototype의 용어가 “원형“이라는 뜻을 알아둘 필요가 있다.

    Prototype 디자인 패턴은 “원형”이 되는 클래스의 객체와 똑같은 것을 복제해서 사용할 수 있게 하는 것이다.

     

    왜 Prototype 디자인 패턴을 사용하는가?

    일반적으로 Something 클래스를 이용하여 객체를 만들때 new Something()과 같이 new 연산자를 이용해서 생성하게 된다. 이 경우 10개 만들면 new연산자를 10개 써야한다. 하지만 Prototype 디자인 패턴을 이용하면 new 연산자를 사용하지 않는다. 대신 복사(복제)하는 방법을 이용한다. 이 패턴은 어떤 클래스의 객체를 생성하는 것이 자원과 시간을 많이 잡아먹거나 복잡한 경우에 유용하게 쓰일 수 있다. 또한 객체를 생성하는 부분이 필요한 상황일때, 생성하려는 객체의 종류정보가 컴파일 시간이 아닌 실행시간에 정해지는 경우에도 필요하다.

    Yuki Hiroshi가 지은 “Java 언어로 배우는 디자인 패턴 입문”에서 다음 경우에는 기존의 객체를 가지고 복사(복제,clone생성)해서 사용하는 경우가 유용할 수 있다고 한다.

     

    1. 취급하는 객체의 종류가 너무 많아 다수의 소스파일을 작성하는 일이 빗어질때

    마음만 먹으면 얼마든지 많은 객체를 다루게 될 수 있다. 이러한 경우 복사해서 사용하는 경우가 유용할 수 있다.

     

    2. 클래스로부터 인스턴스를 생성하기가 복잡할 때

    가령, 마우스를 사용해서 조작하는 도형 에디터와 같은 애플리케이션에서 도형을 나타내는 객체와 똑같은 것을 만드는 경우 클래스를 이용해 new 연산자로 객체를 생성하는 것보다 인스턴스를 복사해서 만드는 방법이 더 간단할 수 있다.

     

    3. 프레임워크와 생성할 인스턴스를 분리하고 싶을 때

    프레임워크의 기본은 클래스간에 결합도를 줄여주고 확장성을 높여주는 것이 중요하다. 이것은 프레임워크가 특정 클래스에 의존적이지 않게 한다는 의미이다.  가령, 진열장이 프레임워크라고 하자. 진열장안에는 각종 물건이 있다. 진열장에는 어떤 물건이든 들어올 수 있다.  진열장에 있는 물건은 구입자가 그 물건을 사기위한 원본이 된다. 이 원본에는 이름이 있고 구입자가 이 물건을 구입할때 진열장에 있는 물건을 바로 구입하는 것이 아니라 물건과 똑같은 물건이 구입자 손으로 넘어온다. 여기서 중요하게 짚고 넘어갈 것은 진열장(프레임워크)는 어떤 물건(객체)이 들어있는지 알 수 없으나 그 물건이 선택되면 그와 똑같은 물건을 반환해준다.

     

    Prototype 디자인 패턴에서 객체를 복사해서 사용하는 이유에 대해 3가지를 언급했지만 감이 잘 안올것이다. 사실 제대로 이해하기 위해서는 직접 코딩해보고 유용함을 몸소 깨달아야한다. 본인도 new연산자를 사용하는 것과 복사해서 사용하는 것에 대한 장단점을 가려내는데 많이 힘들었지만 하다보니 객체를 복사해서 사용하는 것이 더욱 깔끔한 설계를 이끌어 내는데 도움이 되는 것을 느낄 수 있었다.

     

    깊은 복사와 얕은 복사

    여기서 한가지 짚고 넘어갈 것은 “복사”라는 용어이다. 복사에는 “얕은 복사(Shallow Copy)”와 “깊은 복사(Deep Copy)”가 있다. 얕은 복사는 일종의 참조이다.  ActionScript 3.0의 복사는 대부분 얕은 복사이다.

     

     

    상단 코드의 결과는 어떻게 나올까?

     

    1,2,4

    1,2,3

     

    이라고 생각했다면 아직도 당신은 ActionScript 3.0이 깊은 복사로 되는줄로 착각하고 있는 것이다.

     

    답은

     

    1,2,4

    1,2,4

     

    이다.

     

    앞서 설명했지만 일반적인 복사의 형태는 참조복사의 형태이다. 즉, 얕은 복사이다. 참조한다는 것은 메모리를 공유하고 있다는 것을 뜻한다. 위 코드에서 단순히 var b:Array = a를 통해 b에 a를 복사했기 때문에 a와 b가 전혀 다른 객체를 참고하고 있다는 것을 뜻하지 않는다. 실제로는 a에서 참조하고 있는 데이타를 b도 참고한다는 것을 의미한다. 그러므로 a의 요소가 수정되면 b의 요소도 바뀌는 것이다.

     

     

    이 글에서 다뤄지는 Prototype 디자인 패턴의 복사는 깊은 복사이다.

     

    즉, 앞선 코드에서 a와 b가 있다고 할 때 a와 b의 데이터의 값은 완전히 같지만 서로 다른 메모리의 주소를 참고하도록 한다는 것이다. 일반적으로 일란성 쌍둥이를 생각하면 된다. 모든 유전자정보가 같은 일란성 쌍둥이지만 엄연히 완벽히 다른 2개의 객체인 것이다. Prototype 디자인 패턴에서 객체를 복사한다는 의미는 이러한 의미에서 깊은 복사를 뜻한다.

     

    위 코드에서 깊은 복사의 형태로 바꿔지기 위해서는 다음과 같은 작업이 필요하다.

     

    createClone() 메소드를 통해서 위 코드를 실행하면

    결과는 아래와 같다.

     

    1,2,4

    1,2,3

     

    깊은 복사가 제대로 실행된 것을 볼 수 있다.

     

    createClone() 메소드에 사용된 ByteArray를 통해 객체를 생성하는 것을 유심히 살펴보자.

     

    Java의 경우 Cloneable인터페이스와 java.lang.Object에 clone()이 정의되어 있어서 객체복사(깊은복사)를 매우 쉽게 할 수 있다. 하지만 ActionScript 3.0의 경우 그런 메소드가 존재하지 않는다. 그래서 이와 같이 조금 어려운 객체 복사를 해야한다. 이 또한 클래스의 객체를 완벽하게 복사하기 위해서 쓰기에는 완벽하지 않다. 아래처럼 조금 더 수정될 필요가 있다.

     

     

    위 코드는 createClone() 메소드를 통해 클래스 객체도 완벽히 복사할 수 있도록 만들었다.

     

    메소드내 추가된 코드는 객체을 복사할때 원본 객체의 클래스 유형을 유지하기 위한 부분이다. 기존에 클래스가 등록된 경우에는 getClassByAlias()로 Class가 반환되지만 등록되어 있지 않은 경우 ReferenceError를 발생시킨다. 에러 발생시 registerClassAlias()을 이용해 원본객체의 클래스 유형을 유지시킨다. 이 코드를 넣지 않으면 이 함수를 통해 만들어진 복사된 객체가 Object가 되어 원하는 Class의 원형을 갖추지 못하게 된다. 이 말은 registerClassAlias()를 사용하지 않으면 var a:MyClass = createClone( b ) as MyClass로 사용할 수 없고 var a:Object = createClone( b ) 로 밖에 사용할 수 밖에 없다는 것을 뜻한다. 아래 코드는 이에 대해서 이해하기 위한 예제이다.

     

     

    위 코드에서 registerClassAlias()를 빼면 trace() 결과가 false로 나온다. 이와 관련된 자세한 내용은 “getClassByAlias()와 registerClassAlias() 설명서“를 참고하길 바란다.

     

    Prototype 디자인 패턴을 적용한 UML과 ActionScript 3.0 코드

     

    위 그림은 Prototype의 기본 디자인 패턴에 대한 클래스 다이어그램이다. 사용자인 Client 클래스는 인터페이스 Prototype을 구체적으로 구현한 ConcretePrototype1, ConcretePrototype2…. 를 사용한다. Client 클래스는 ConcretePrototype1, ConcretePrototype2의 존재를 알필요 없이 clone()만 알고 있으면 된다. 이 clone() 메소드가 바로 깊은 복사를 통해서 자신을 복사해주는 역할을 하는 것이다.

     

     

    위 다이어그램은 이제부터 선보일 Prototype 디자인패턴을 적용시킨 클래스들의 관계를 설명하고 있다. 아래 이미지는 다이어그램에 따라서 만들어진 ActionScript 3.0 프로젝트의 Package구조를 보여주고 있다.

     

     

    간단히 설명하자면

    framework 패키지와 car 패키지로 구분되어 있는데 이렇게 구분한 것은 Prototype 디자인패턴이 적용된 framework 패키지 안에 클래스를 이용하여 계속 확장할 수 있음을 뜻한다. 가령, car 뿐아니라 boat, television, radio 어떤 것이든 상관 없다는 것이다.

    Prototype의 핵심 인터페이스인 IPrototype을 확장한 Product를 또 Engine과 Wheel 클래스로 확장했다. 또한 IPrototypeClient 인터페이스를 확장해 ProductManager를 framework에 만들고 이것을 확장한 CarPartManager 클래스를 볼 수 있다.  CarPartManager 클래스에 Engine, Wheel의 객체를 등록하면 CarPartManager의 create()메소드를 통해 Engine과 Wheel의 객체의 새로운 객체를 복제할 수 있다.

    이해를 돕기 위해 사용예를 보자.

     

    CarPartManager의 객체에 2개의 Wheel 객체와 2개의 Engine 객체를 등록하고 create() 메소드를 통해 새로운 객체를 생성하고 있다. 결과는 다음과 같이 나온다.

     

    This is a wheel A
    This is a wheel B
    This is a engine 1
    This is a engine 2

     

    중요하게 체크해야할 사실은 등록할때 외에는 Wheel과 Engine의 객체 복사를 new를 통해하지 않는다는 것이다. 단지 처음에 Wheel과 Engine를 CarPartManager에 register()메소드의 첫번째 인자값만 가지고 등록된 객체를 복사해낸다. Prototype 디자인 패턴은 이렇게 new를 사용하지 않고 등록시 사용한 별칭(wheel1, wheel2, engine1, engine2)만 가지고 똑같은 객체를 복사생성하기 때문에 new를 통한 생성의 복잡함(설정이 매우 많은 경우)이 있는 경우 크게 도움이 될 수  있다.

    각각의 클래스 코드를 살펴보자.

     

    먼저 인터페이스이다.

     

    IPrototype은 매우 단순하다. clone() 메소드를 통해서 복사만 가능하도록 구현할 수 있으면 된다.

     

     

    IPrototypeClinet는 복사가 가능한 인터페이스(IPrototype)를 가지는 객체를 특별히 지정된 이름으로 등록할 수 있고 등록된 객체로 부터 지정된 이름으로 생성할 수 있도록 하는 것이 목적이다.

     

    위 2개의 핵심 인터페이스를 구현한 프레임워크내 2개의 핵심 클래스를 살펴보자.

     

    위 클래스는 IPrototype을 구현한 Product클래스이다. 앞서 소개했던 CloneMachine클래스를 확장해서 만들어졌다. clone()메소드를 구현한 부분을 살펴보면 CloneMachine 클래스에 정의된 createClone()메소드를 이용하여 자기 자신을 복제하여 넘겨주는 것을 확인할 수 있다. Product클래스는 일종의 생산품의 부모라고 생각하면 된다. 이 생산품을 설명해주는 description get/set 메소드도 추가되어 있는 것을 확인하자. 이 클래스는 단독으로 쓰이지 않으며 반드시 확장해서 사용한다. 일종의 추상클래스라 생각하면 된다. ActionScript 3.0에서는 추상클래스로 지정하기 위해 Java의 abstact와 같은 키워드가 없으므로 별도의 설정은 하지 않았다.

     

     

    위 클래스는 IPrototypeClient 인터페이스를 확장한 ProductManager 클래스이다. 이 클래스는 등록(register)와 복제(create)를 구현하고 있다. 코드는 그리 어렵지 않다. create()메소드에 등록된 객체를 찾아 clone()메소드로 복사하는 것을 꼭 확인하기 바란다.

     

    프레임워크 부분은 모두 설명했다. 이제 활용 예제로 만든 car 패키지 부분을 살펴보자.

     

    IPrototype을 구현한 Product를 확장해 Wheel과 Engine 클래스를 만들었다. 자동차 부속품인 바퀴휠과 엔진을 의미한다. 단순히 생성자만 새로 만들었을뿐 별 다른 구현은 하지 않았지만 필요하다면 다른 구현을 해도 무방하다.

     

     

    IPrototypeClinet를 구현한 ProductManager를 확장해 CarPartManager를 만들었다. 더이상 다른 정의를 하지 않았고 ProductManager가 구현한 것을 그대로 쓰고 있지만 원하는데로 확장해서 사용해도 문제없다. 의미적으로는 자동차 부속품을 관리하는 관리자 클래스라고 생각하면 되겠다.

     

    다시 한번 이 클래스를 사용하는 예제를 보자.

     

     

    이제 어렵지 않게 해석할 수 있을것이다.

     

    이것만은 꼭 기억하자. Prototype 디자인 패턴은 객체를 원하는데로 복사(깊은복사)해서 사용할 수 있게 만든 패턴이다. Prototype 패턴을 이용해 Flex/AIR/Flash/ActionScript 3.0 프로그램 설계에 도움이 되길 바란다.

     

    참고자료

    ActionScript 3.0 Prototype Design Pattern: A Minimalist Example

    ActionScript 3.0 Clone: A Prelude to the Prototype Design Pattern

    getClassByAlias()와 registerClassAlias() 설명서

    소스

    -> 무료 UML 툴인 StarUML을 다운로드 받아 보실 수 있습니다.

    -> Flex Builder 3 에서 Import 해 사용하세요.

     

    추가내용

    Flex에 관련된 컴포넌트는 여기서 소개된  Prototype 방식으로 clone()을 생성하면 에러가 생성될 가능성이 많다. 특히 UIComponent 계열은 Flex SDK 프레임워크 종속적인 클래스이다. 이 것으로 만들어진 객체를 clone()으로 깊은 복사에 성공하더라도 프레임워크 자체에서 UIComponent 객체들을 관리하기 때문에 단순한 clone() 메소드로는 Flex 자체에서 동작하도록 만들 수 없다. 그러므로 프레임워크와는 상관없는 Sprite, Shape등을 이용해서 만들도록 하자.

    추가내용 2 (2009.12.9)

    히카님과 대화하면서 새롭게 안 사실입니다. 위에서 객체 복사를 위한 부분에 대해서 실제로 메서드, 변수정의는 모두 되지만 ByteArray, Vector, Array, Dictionary, Object 값외에 다른 값의 복사는 되지 않는다고 합니다. 그래서 결국 이 방법으로 복사하는 것은 잘못된 것이네요. 참고하세요.

    Adobe Flex를 접할때 어려워하는 부분중에 하나가 Event입니다.
    깊숙히 이해하려고 할때 잘 이해가 안되는 부분이 많은게 이벤트죠.

    기본에 충실해야한다!
    프로젝트(http://starpl.com)를 하면서 많이 느꼈습니다.
    처음 Flex 접했을때 가장 어려움을 겪었던 것은 기본이 탄탄하지 못한 것으로부터 였습니다.
    그래서 Flex와 함께 ActionScript 3.0의 기본 문법 및 대략적 내용을 이해해야한다고 생각합니다.
    단순한 문법뿐 아니라 분석/설계에도 관심을 가지고 공부해서 명확하고 깨끗한 프로그램을 만들 수 있도록 노력해야겠구요.

    아래는 Event에 대한 질문을 걸어놓고 스스로 답변까지 해두었습니다.
    이것으로 Event 전부를 설명할 수 없지만 몇가지 궁금한 사항을 이해하는데는 도움이 될겁니다.
    그냥 읽고 지식만 얻어가지 마시고 없는 질문이 있다면 스스로 질문/답변을 댓글로 달아주시면 더욱 고맙겠습니다. 잘못된 내용이나 추가해야한다는 것도 포함합니다.


    1. ActionScript 3.0 이벤트(Event)

    1.1 ActionScript 2.0과 3.0에서 이벤트가 바뀐점은?

    • ActionScript 2.0에서는 이벤트 리스너를 추가하는 경우 상황에 따라 addListener() 또는 addEventListener()를 사용하지만 ActionScript 3.0에서는 항상 addEventListener()를 사용합니다.
    • ActionScript 2.0에는 이벤트 흐름이 없으므로 이벤트를 브로드캐스팅하는 객체에서만 addListener() 메서드를 호출할 수 있지만 ActionScript 3.0에서는 이벤트 흐름에 포함된 모든 객체에서 addEventListener() 메서드를 호출할 수 있습니다.
    • ActionScript 2.0에서는 함수, 메서드 또는 객체를 이벤트 리스너로 사용할 수 있지만 ActionScript 3.0에서는 함수 또는 메서드만 이벤트 리스너로 사용할 수 있습니다.
    • on(event) 구문은 이제 ActionScript 3.0에서 지원되지 않으므로 ActionScript 이벤트 코드를 무비 클립에 첨부할 수 없습니다. 이벤트 리스너를 추가할 때는 addEventListener()만 사용할 수 있습니다.


    1.2 Event를 사용해야하는 이유? 2가지 이상

    • 객체간 강한 결합 해결 : 애플리케이션 설계시 Callback함수를 사용하는 등의 강한결합을 이용하는 경우 유지보수 및 확장이 어려워지는 경우가 크다. 이벤트는 객체간 느슨한 결합을 해주는데 큰 힘을 발휘할 수 있다.
    • 작업에 대한 일관성 없는 응답 : 동기적 방식과 비동기적 방식에 대한 응답에 대한 처리를 이벤트를 사용하지 않는 경우 서로 다른 방법을 선택해서 사용해야한다. 이벤트는 1가지 방법으로 응답처리를 가능하게 해준다.


    2. Event의 구성요소 3가지는 무엇이고 의미를 설명하라.

    • 이벤트 객체(Event Object)
      flash.net.Event의 객체나 이를 확장한 이벤트 객체를 의미한다. 이벤트 객체에는 이벤트에 대한 정보가 들어가있다. 이벤트의 종류(Type)과 이벤트를 발생한 객체(target)에 대한 정보가 포함된다.
    • 이벤트 발생자(Event Dispatcher)
      이벤트를 발생(broadcast)하는 객체이다. 이벤트 객체에 target으로 이벤트 발생자 정보가 담긴다. flash.net.EventDispatcher를 확장한 모든 객체는 이벤트 발생자가 될 수 있다. EventDispatcher의 dispatchEvent() 메소드에 이벤트 객체를 담아 실행하면 이벤트가 발생하게 된다.
    • 이벤트 청취자(Event Listener)
      일종의 메소드 또는 함수이다. 발생한 이벤트를 듣는 역할을 한다. EventDispatcher클래스를 확장한 클래스의 객체에서 addEventListener()을 이용해 이벤트 청취자를 등록할 수 있다. addEventListener()을 이용하면 청취할 이벤트의 종류(type)을 결정할 수 있으며 청취할 이벤트 흐름(capture,target,bubbling) 및 우선순위등을 선택할 수 있다.


    3. Event Listener를 등록/삭제하는 방법을 기술하라.

    • 등록
      EventDispatcher의 addEventListener() 메소드를 이용한다.
      예)
      var button:Button = new Button();
      button.addEventListener( MouseEvent.CLICK, onClick, false );
      public function onClick( event:MouseEvent )
      {
        trace( event.type, event.target  );
      }
    • 삭제
      EventDispathcer의 removeEventListener() 메소드를 이용한다.
      예)
      button.removeEventListener( MouseEvent.CLICK, onClick, false );
      3개의 인자가 addEventListener()를 사용할때와 같아야 정상적으로 이벤트 청취자 등록이 삭제된다.
      참고로 맨 마지막 인자는 useCapture이다.


    4. Event 전파단계의 종류 3가지가 무엇이며 각각에 대한 설명

    • 이벤트 전파단계의 종류 : 캡쳐(capture) 단계, 타겟(target) 단계, 버블(bubble)단계
    • 이벤트 전파 순서 : 캡쳐->타겟->버블
    • 캡쳐 단계
      루트(root) 디스플레이 객체서부터 이벤트를 발생한 객체(target)까지 디스플레이 객체 리스트(display Object List)를 따라 검색해나가는 단계이다. 즉, 부모->자식으로 이벤트를 전파한다. 이 캡쳐 단계에 이벤트를 청취하기 위해서는 addEventListener()의 3번째 인자인 useCapture가 반드시 true로 되어야 한다. false로 되어 있다면 캡쳐단계가 아닌 타겟과 버블 단계에서 이벤트 청취를 한다.
      캡쳐단계 청취하는 목적은 다양할 수 있다. 가령 중간에 이벤트 전파를 중지시킬때 필요할 수 있다. 이해가 안될 수 있지만 실제 프로젝트를 하면 이런 경우가 다분하다.
      사용 예 )
      button.addEventListener( MouseEvent.CLICK, onClick, true );
    • 타겟 단계
      갭쳐 단계 이후 타겟단계에 도달한다. 타겟 단계는 이벤트를 발생시킨 객체까지 도달했을때 이뤄진다.
    • 버블 단계
      타겟 단계 이후 다시 루트(root) 디스플레이 객체로 이벤트가 전파되는 단계이다. 버블버블 물망울이 수면위로 다시 올라가는 것을 연상하면 되겠다. 캡쳐와 반대로 자식->부모로 이벤트가 전파되는 단계이다.
      이벤트가 버블 단계로 전파되기 위한 조건은 이벤트 송출시 Event의 생성자 인자값중 bubble값을 true로 설정되어 있어야 한다. MouseEvent같은 경우 이 bubble값이 true로 설정되어 있다.
      타겟단계와 버블단계에 이벤트 청취자를 등록하기 위해 addEventListener()의 3번째 인자 useCapture가 false로 되어야 한다. useCapture를 설정하지 않으면 기본값이 false이다.
    • 이벤트 객체가 전파되는 기능이 굳이 필요한 이유
      부모-자식1-자식2....-자식100 의 형태로 디스플레이 객체 리스트가 형성되어있다고 가정하자 자식50에서 자식 100을 마우스 클릭했을때 이벤트를 받고 싶다고 하자. 자식50은 자식100의 존재유무를 모른다(알 수 있지만 알게되면 강한결합이 일어난다.) 이런 경우 이벤트 객체가 전파되는 기능은 매우 유용하게 쓰일 수 있다. 캡쳐나 버블 단계에서 자식50은 자식100에서 발생된 이벤트를 받을 수 있다.
    • flash.display.DisplayObject 클래스는 EventDispatcher를 확장해서 만들어졌다는 것에 주목하자.


    5. 4번의 Event 전파는 어떤 환경에서 가능한가?

    • 반드시 디스플레이 객체 리스트(Display Object List) 상에 존재하는 디스플레이 객체(Display Object)여야 이벤트 전파가 가능하다. 그 외의 이벤트 전파는 타겟 단계밖에 없다. 가령, HTTPService나 URLLoader에서 이벤트가 발생하는 경우 이들은 디스플레이 객체가 아니다.
    • 이벤트 전파의 조건
      1. 디스플레이 객체여야 한다.(flash.display.DisplayObject를 확장한 클래스의 객체)
      2. 디스플레이 객체는 디스플레이 객체 리스트에 존재해야 한다. 즉, stage로 부터 addChild되어 있어야 한다.

    6. 이벤트 전파시 target과 currentTarget의 차이점은 무엇인가? 예를 들어 설명하라.

    • target은 이벤트를 발생한 객체이고 currentTarget은 이벤트를 청취하는 객체이다.
    • 예시)
      var p:Sprite = new Sprite(); //parent
      var c:Sprite = new Sprite(); //child
      addChild( p );
      p.addChild( c ); //c가 p의 자식인 것에 주목
      p.addEventListener( MouseEvent.CLICK, onClick ); //이벤트 청취를 p가 하는 것에 주목!
      public function onClick( event:MouseEvent )
      {
      trace( event.target, event.currentTarget ); //c를 클릭했을 경우 c, p가 나오고 p를 클릭했을 경우 p,p가 나온다.
      }


    7. Event 우선순위에 대해서 간단한 예제를 들어 설명하라.

    • 이벤트 우선순위는 구체적으로 이벤트 청취 우선순위라고 할 수 있다.
    • 이벤트 청취 우선순위는 addEventListener()의 4번째 인자인 priority를 이용한다. 이 값이 클 수록 청취 우선순위가 높아지며 addEventListener()의 등록 순서에 상관없이 우선순위에 따라 이벤트 리스너가 호출된다.
    • 이벤트 청취 우선순위가 유효한 조건
      1. 같은 객체에서 청취한다.
      2. 같은 이벤트 종류(type)을 청취한다.
      3. 같은 useCapure 값을 가진다.
    • ex)
      a.addEventListener(MouseEvent.CLICK, aHandler, false, 3);
      a.addEventListener(MouseEvent.CLICK, bHandler, false, 1);
      a.addEventListener(MouseEvent.CLICK, cHandler, false, 100);

      a객체에 MouseEvent.CLICK 이벤트가 발생시 등록순서에 상관없이 priority값이 가장큰 cHandler가 먼저 호출되고 다음으로 aHandler, bHandler가 호출된다.

    8. Event 전파를 중단하는 메소드 2개가 무엇이며 간단한 예제를 들어 각각의 차이점을 설명하라.

    • Event 클래스의 stopPropogation()와 stopImmediatePropogation() 메소드이다.
    • 차이점
      - stopPropogation()
        이벤트 전파를 중단하되 같은 노드의 이벤트를 청취까지는 허락한다.
      - stopImmediatePropogation()
        이벤트 전파를 즉시 중단한다.
    • 예제
      back이 배경 canvas이고 button1이 back에 포함된 button일때
      back.addEventListener(MouseEvent.CLICK, event1Handler);
      back.button.addEventListener(MouseEvent.CLICK, event2Handler);
      back.button.addEventListener(MouseEvent.CLICK, event3Handler);
      back.button.addEventListener(MouseEvent.MOUSE_OUT, outHandler);

      private function event1Handler(e:MouseEvent):void
      {
             trace('back');
      }
      private function event2Handler(e:MouseEvent):void
      {
             trace('button');
             e.stopPropagation();
             e.stopImmediatePropagation();
      }
      private function event3Handler(e:MouseEvent):void
      {
             trace('button2');
      }
      private function event1Handler(e:MouseEvent):void
      {
             trace('out');
      }

      e.stopPropagation(); e.stopImmediatePropagation(); 두가지 다 없을경우
              button
              button2
              back
              out
      e.stopPropagation(); 만 실행
              button
              button2
              out
      e.stopImmediatePropagation(); 만 실행
              button
              out
       


    9. addEventListener의  useCapture 인자는 무엇을 의미하는가? 간단한 예제를 들어 설명하라.

    • 이벤트 전파를 청취할때 캡처단계를 청취할지 타겟 및 버블 단계를 청취할지 결정하는 인자이다.
    • 모든 전파 단계에서 청취하기 위해서는 useCapture가 다른 2개의 addEventListener()를 사용해야한다.
    • 예제 생략


    10. Sprite, Shape와 같은 클래스에서 어떻게 하면 이벤트를 송출할 수 있는가?

    • dispatchEvent() 메소드를 호출하면 된다.


    11. Sprite, Shape와 같이 이벤트 송출할 수 있는 클래스는 근본적으로 어떤 클래스를 상속받았기 때문에 가능한가?

    • Sprite와 Shape는 궁극적으로 DisplayObject를 확장해서 만들어졌으며 DisplayObject는 EventDispatcher를 확장했다. 그러므로 Sprite와 Shape와 같은 클래스는 EventDispatcher의 메소드를 전부 사용할 수있다. 이중에 dispatchEvent()도 사용할 수 있다.


    12. IEventDispatcher는 무엇이며 어떤 경우에 이것을 사용해야할까?

    • EventDispatcher의 인터페이스이다.
    • IEventDispatcher를 활용하는 것은 다중상속이 불가능한 AS3에서 유용하다.
      가령, 어떤 클래스가 ProxyObject를 확장해서 만들어져야한다. 그런데 이 클래스는 이벤트도 발생시켜야 하므로 EventDispatcher클래스도 확장해야한다. ActionScript 3.0은 다중상속을 지원하지 않는다.(다중상속은 이점도 있지만 단점도 있다.) 그러므로 이러한 경우에는 주가 되는 클래스인 ProxyObject를 is-a 확장을 하고 EventDispatcher를 has-a 확장을 감행한다음 만들어진 클래스가 EventDispatcher의 기능을 수행한다는 것을 명시적으로 외부에 알려주고 내부적으로 구현시켜주기 위해 IEventDispatcher 인터페이스를 implements 해준다. 이렇게 하면 원하는 기능인 ProxyObject와 EventDispatcher의 기능을 전부 수행하면서도 다중상속의 문제를 극복할 수 있다.
    • ex)
      public class MyProxyObject extends ProxyObject implements IEventDispatcher
      {
        var evnetDispatcher:EventDispatcher = new EventDispatcher();
       
        //내부적으로 IEventDispatcher에 선언한 메소드를 구현한다.  가령...
        public function dispatchEvent( event:Event ):Boolean
        {
           return  eventDispatcher.dispatchEvent( event );
        }
      }


    13. preventDefault()와 stopPropagation(), stopImmediatePropagation()의 근본적 차이점은 무엇인가?

    • preventDefault()는 기본 동작(dafault behaviors)를 취소하는데 쓰이는 메소드인 반면 stopPropagation(), stopImmediatePropagation()는 이벤트 전파에만 영향을 준다.
    • preventDefault()가 사용 가능한지는 isDefaultPrevented() 메소드로 알 수 있다.

    14. preventDefault()를 호출해서 실행을 가능하게 하기 위한 조건은 무엇인가?

    • 이벤트 객체를 생성시 cancelable속성이 true로 설정되어 있어야 한다.


    15. 커스텀 이벤트를 만들어야하는 어떤 경우이며 어떻게 만드는가?

    • 커스텀 이벤트는 기본 이벤트 클래스(flash.events.Event) 또는 그 상위 이벤트 클래스를 확장하는 경우를 의미한다.
    • 이벤트를 확장해야하는 경우는 이벤트 송출시에 필요한 정보를 담아내기 위함이다.
    • 만드는 방법
      class MyEvent extends Event
      {
        public static const MY_EVENT:String = "myevent";
        public var value:String;
        public function MyEvent( type:String, value:String ):void
        {
           super( type, false, false );
           this.value = value;
        }
        overide public function clone():Event
        {
          return new MyEvent( type, value );
        }
      }



    16. 커스텀 이벤트가 bubbling이 가능하기 위해 어떤 조건이 필요한가?

    • Event의 생성자의 2번째 인자인 bubble값을 true로 설정한다.

    17. 커스텀 컴포넌트 만들때 clone()함수를 override해야하는 이유는 무엇인가?

    • clone()메소드는 기존과 신규 properties들을 clone 내로 설정해 이벤트 객체의 복사본을 돌려준다.
      clone() 메소드를 Event 클래스 확장시 오버라이드 해야하는 이유는 이벤트를 다시 재전달해야하는 경우 묵시적으로 복사해서 재전달할 필요가 있을때 필요하기 때문이다.
    • 참고로 toString()도 확장해서 사용하는 것이 좋다. 이벤트에 대한 명세서가 될 수 있기 때문이다.

    18. 약참조에 대해서 GC(가비지 콜렉션)과 관련되어 설명하라.

    • addEventListener()함수로 이벤트 청취자를 등록할때 5번째 인자로 useWeakReference 인자가 있다. 이것을 true로 설정하면 이벤트 청취자가 이벤트 청취를 약참조를 하겠다는 의미이다. 반대로 false(기본)이면 강참조이다.
    • 약참조, 강참조를 설명하기 전에 가비지 콜렉션(GC, Garbage Collection)에 대해서 먼저 알아야한다.
      만들어졌던 객체가 더이상 쓸모가 없어졌을때 GC라고 한다. 쓸모가 없어졌다는 것은 참조의 고리가 끊어졌다는 것을 의미한다. 메모리에 상주해 있지만 조만간 메모리에서 없애야할 대상인 것이다. Flash Player는 GC로 선정된 객체들을 메모리에서 삭제하는 시점을 내부적으로 구현해놓고 있다.(많은 분들이 Flash Player GC처리를 못한다고 하지만 실제로는 문제없이 잘하고 있다. 단지 효율적으로 GC처리를 하는지는 의문이지만...)
      Flash Player GC 대상을 찾아내기 위한 방법은 2가지가 있다. 그것은 레퍼런스 카운팅(reference counting)과 마크 앤 스윕(Mark and Sweep)이다. 레퍼런스 카운팅은 생성된 객체가 참조할때마다 참조 카운트를 1씩 더해준다. 또 참조에서 끊어지는 경우 1씩 감소시켜준다. 만약 레퍼런스 카운팅 값이 0이되는 경우 바로 GC 대상이 되는 것이다. 마크 앤 스윕은 레퍼런스 카운팅 만으로 찾을 수 없는 GC대상을 찾아준다. 가령, 두개 이상의 객체가 서로 다른 객체를 참조하고 있다고 가정한다.(순환참조) 그럼 이들 레퍼런스 카운팅 값은 1 이상이다. 그러므로 레퍼런스 카운팅 방법으로는 GC대상을 만들 수 없다. 마크 앤 스윕은  이들 객체가 최상위 부모와 연결되어 있는지 검사하는 방법이다. 부모로 부터 꼬리에 꼬리를 물고 객체가 참조되어 있는 것인지 조사해나가는 방법으로 GC대상을 검색한다.
      레퍼런스 카운팅과 마크앤 스윕 방법으로 찾아낸 GC대상은 메모리에 상주되지만 Flash Player의 GC 삭제방법 로직에 의해 어느 순간 삭제된다.(이 시점은 프로그래머가 어찌할 수 없다. AIR 또는 Flash Player Debug버전의 경우에만 System.gc()를 이용해 강제적으로 GC를 메모리에서 삭제할 수 있긴 하다.)
    • addEventListener()의 useWeakReference를 true로 설정하면 약참조가 되어 레퍼런스 카운팅을 증가시키지 않는다. 강참조의 경우 이벤트 대상(target)의 참조를 지웠다고 해도 addEventListener로 등록한 청취자 메소드가 GC대상이 아니라면 이벤트 대상의 레퍼런스 카운팅 값은 0이 될 수 없다. 결국 프로그래머의 뜻과 다르게 메모리를 낭비하는 일을 초래할 수 있다. 물론 이벤트 대상이 사라지는 시점에 removeEventListener() 사용하면 되겠지만 때로는 그 시점을 모르는 경우도 발생한다. 이러한 경우 useWeakReference를 true로 설정해서 메모리 낭비를 없앨 수 있겠다.
    • Flex에서 List 계열의 컴포넌트를 살펴보면 dataProvider()에 ArrayCollection 객체를 받을때 ArrayCollection의 변화를 살펴보기 위해 addEventListener()를 사용한다. 그런데 ArrayCollection 객체는 언제 어떻게 될지 List 계열 컴포넌트는 알 수 없을 것이다. 이때 유용하게 사용하는 것이 바로 useWeakReference=true로 설정해서 필요없어진 ArrayCollection이 메모리 남는 것을 방지할 수 있다. 참고 : http://blog.jidolstar.com/375



    19. 이벤트가 전파되는 중간에 전파를 멈추게 하기 위해 stopPropagation()을 사용하지 않고removeEventListener() 를 사용하면 안되는 이유를 설명하라. (힌트, ActionScript 3.0의 Native 메커니즘에 입각한다. http://bixworld.egloos.com/2149717)

    • 이벤트 송출을 시작하면 flash player는 이벤트가 송출될 디스플레이 객체 리스트를 전부 조사해 미리 이벤트 객체를 복사해두기 때문이다. 이미 전파할 대상이 모두 정해져있고 전파가 시행중인 경우에는 중간에 removeEventLisener를 사용하더라도 일단 송출된 이벤트는 끝까지 전파된다. 다음 시점 이벤트 송출시에는 removeEventLisener()가 적용되어 있는 상태가 된다.


    20. willTrigger()와 hasEventListener()의 의미를 설명하고와 두 메소드의 차이점을 보여줄 수 있는 대한 예를 들어보자.

    • 공통점
      hasEventListener()이나 willTrigger() 모두 이벤트 청취자가 있는지 검사한다.
    • 차이점
      hasEventListener() : 지정한 객체에(만) 이벤트 청취자가 있는지 조사한다. 이벤트 흐름과는 무관하다.
      willTrigger() : 지정한 객체를 포함하는 디스플레이 객체 리스트에 해당 이벤트가 청취자가 있는지 조사한다. 그러므로 이벤트 흐름과 관련 있다.
    • 예제
      var button:Sprite = new Sprite();
      addChild( button );
      this.addEventListener( MouseEvent.CLICK, onClick );
      trace( button.willTrigger( MouseEvent.CLICK ) ); //true
      trace( button.hasEventListener( MouseEvent.CLICK ) ); //false ->왜 false가 되는가 아는게 중요!
      trace( this.willTrigger( MouseEvent.CLICK ) ); //true
      trace( this.hasEventListener( MouseEvent.CLICK ) ); //true
    • 이 두개의 함수를 잘 활용하면 쓸데없이 이벤트를 송출하는 경우를 방지할 수 있다.

    21. ActionScript 3.0 이벤트에 대해 참고할 만한 문헌 및 자료를 조사하자.
    Introduction to event handling in ActionScript 3.0
    Jasu님의 ActionScript 3.0 이벤트 오브젝트
    Event 한글 문서
    EventDispatcher 한글 문서


    글쓴이 : 지돌스타(http://blog.jidolstar.com/415)

    + Recent posts