Googling으로 구한 소스를 진행중인 프로젝트에 Copy&Paste를 해서 개발하는 도중 제가 의도치 않은 곳에서 이벤트가 발생하는 경우가 있었습니다.  뜻하지 않은 현상으로 엄청난 삽질을 했습니다. 원인은 Flex에서는 이벤트 흐름이라는 것이 존재한다고 합니다. 지금까지는 그냥 addEventListener()만 붙이고 동작유무에 이상이 없었으니 이벤트 흐름따위한테는 관심이 없었더랫죠. 이벤트가 발생하는 background에서 어떤 특수한 흐름이 존재하리라고는 꿈에도 생각을 못했었습니다.  그리하여 미루고 미뤄왔던 이벤트 흐름을 제 나름대로(?) 심도있게(?) 공부한 글입니다. 아직 초보개발자의 글인 만큼 부족한 점이 많을 수 있으니, 잘못된 점이나 보완해야할 점 등 어떠한 feedback도 환영합니다.

 

Flex에서 이벤트가 송출(dispatch)될 경우, 어떤 특수한 단계의 이벤트 전파(propagation)가 일어납니다.  그 특수한 단계는 3단계로 이루어져 있었습니다.

 

Capture -> Target -> Bubble

 

각 단계에 대한 설명은 다음과 같습니다.  * okgosu의 플렉스4.5 플래시 빌더정석中

  캡쳐링capturing 단계

 이벤트가 발생하면 최상위 컴포넌트부터 이벤트가 발생한 바로 상위 컴포넌트까지 차례로 이벤트 리스너가 있냐 없냐를 체크하는 단계

  타겟팅targeting 단계

 캡쳐단계에서 발견한 이벤트 리스너를 호출하는 단계 

  버블링bubbling 단계

 타겟 단계에서 상위 컴포넌트로 이벤트 리스너들을 체크하는 단계

  

이해를 돕기위해 다음의 예제를 만들어보았습니다.

<?xml version="1.0" encoding="utf-8"?>

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"

                     xmlns:s="library://ns.adobe.com/flex/spark"

                     xmlns:mx="library://ns.adobe.com/flex/mx"

                     width="700" height="252"

                     creationComplete="init()">

      <s:layout>

            <s:HorizontalLayout verticalAlign="middle" horizontalAlign="center"/>

      </s:layout>

     

      <fx:Script>

            <![CDATA[

                 

                  private function init():void

                  {

                        myBox1.removeEventListener(MouseEvent.CLICK, showPhase, !capturing.selected);

                        myBox2.removeEventListener(MouseEvent.CLICK, showPhase, !capturing.selected);

                        myBox3.removeEventListener(MouseEvent.CLICK, showPhase, !capturing.selected);

                        myBox4.removeEventListener(MouseEvent.CLICK, showPhase, !capturing.selected);

                       

                        myBox1.addEventListener(MouseEvent.CLICK, showPhase, capturing.selected);

                        myBox2.addEventListener(MouseEvent.CLICK, showPhase, capturing.selected);

                        myBox3.addEventListener(MouseEvent.CLICK, showPhase, capturing.selected);

                        myBox4.addEventListener(MouseEvent.CLICK, showPhase, capturing.selected);

                  }

                 

                  private function showPhase(event:MouseEvent):void

                  {

                        monitor.text += "event.target.name : " + event.target.name + ", "

                                          + "event.currentTarget.name : " +  event.currentTarget.name + ", "

                                          + "event.eventPhase : " + event.eventPhase

                                          + "\n";

                  }

            ]]>

      </fx:Script>

     

      <mx:VBox id="myBox4" width="200" height="200" backgroundColor="#FF0000" horizontalAlign="center" verticalAlign="middle" name="BOX4">

            <mx:VBox id="myBox3" width="150" height="150" backgroundColor="#00FF00" horizontalAlign="center" verticalAlign="middle" name="BOX3">

                  <mx:VBox id="myBox2" width="100" height="100" backgroundColor="#0000FF" horizontalAlign="center" verticalAlign="middle" name="BOX2">

                        <mx:VBox id="myBox1" width="50" height="50" backgroundColor="#FFFF00" name="BOX1">

                        </mx:VBox>

                  </mx:VBox>

            </mx:VBox>

      </mx:VBox>

     

      <s:VGroup>

            <s:TextArea id="monitor" width="450" height="200"/>

            <s:HGroup>

                  <s:CheckBox label="isCapture" id="capturing" change="init()"/>

                  <s:Button label="Clear" click="monitor.text=''" />

            </s:HGroup>

      </s:VGroup>

</s:Application>

*source :EventFlow.fxp

 

여러개의 VBox컴포넌트들을 위와같이 부모-자식으로 묶여져 있습니다. 각 색깔을 클릭해보면 이벤트의 흐름을 확인하실 수 있습니다.  useCapture의 Default값은 false이므로 초기 캡쳐링 단계는 생략됩니다.  target은 이벤트가 발생된 근원지를 나타내고 있으며, currentTarget은 현재 이벤트가 발생되고있는 지점을 나타내고 있습니다. phase는 이벤트 단계를 나타냅니다. ( phase1:Capturing, phase2:Targeting, phase3:Bubbling)     체크박스에 체크를 하면 Capturing단계에서만 리스닝하게 되고, 체크를 풀면 Targeting,Bubbling단계에서만 리스닝을 하게 됩니다. 세단계 모두에 리스너를 달고 싶으면 removeEventListener를 제거해주면 됩니다.

 

이벤트에 관한 문서의 내용을 요약하면 다음과 같습니다.

 

* Flex에서의 이벤트 특징

 

①  한번 리스너 이벤트가 성공적으로 등록되면, 추가적으로 등록되는 이벤트들과의 우선순위를 바꿀 수 없습니다. 우선순위를 바꿀 수 있는 유일한 방법은 removeListener()를 한 다음 다시 등록하는 방법밖에 없습니다.

 

②  useCapture 옵션을 다르게 등록하면 별도의 리스너가 등록됩니다. 예를들면 다음과 같은 코드가 있다고 가정한다면, '마징가'에는 두개의 리스너가 달리는 것입니다. 여기서  주의하실 점은 각각의 설정한 단계에서만 리스닝을 한다는 것입니다.

    

     마징가.addEventListener(MouseEvent.Click, test, true);         // Capture단계에서만 Listening 함.

     마징가.addEventListener(MouseEvent.Click, test, false);        // Target, Bubble단계에서만 Listening 함.

 

target단계에서만 리스너를 등록을 하고싶다거나, bubble단계에서만 리스너를 등록한다나 하는것은 불가능 합니다. 그 이유는 bubble단계는 오직 target노드의 조상에게만  버블링 할 수 있도록 되어있기 때문입니다.

 

송출된 객체가 존재하면(useWeakReference가 true가 아니라면) 가비지 콜렉터는 메모리에서 자동적으로 리스너를 제거하지 않기 때문에, 리스너가 필요하지 않게 되면 필히  removeEventListener() 하십시오.                                                                                                *이미지 출처 : ActionScript 도움말

 

⑤ EventDispatcher 인스턴스를 복사한다고 해서 리스너까지 복사되는 것은 아닙니다. 하지만 EventDispatcher 인스턴스를 옮기는 경우에는 리스너까지 따라갑니다.

 

⑥ If the event listener is being registered on a node while an event is being processed on this node, the event listener is not triggered during the current phase but can be triggered during a later phase in the event flow, such as the bubbling phase. 

 

⑦  If an event listener is removed from a node while an event is being processed on the node, it is still triggered by the current actions. After it is removed, the event listener is never invoked again (unless registered again for future processing).

 

 

 

* addEventListener() 메소드 파라미터

 

flash.display.Stage.addEventListener(type:String

     , listener:Function

     , useCapture:Boolean=false

     , priority:int=0

     , useWeakReference:Boolean=false):void 

 

 

 type 

 

 - 이벤트 타입

 

 userCapture

 

 - 리스너를 (Capture단계) or (Target, Bubble단계) 어디에 붙일 지를 정함

 - true 설정하면 Capturing .

 - false 하면 Targeting Bubbling .

 - Capture,Target,Bubble 단계 모두 리스닝 하려면위의 '마징가' 같이 addEventListener 두번 붙여줘야함.

 

 priority

 - 리스너의 레벨 우선순위 정함.

 - 32비트 integer 지정함.

 - 높은 수일수록 우선권이 높음.

 - 우선순위가 똑같으면 추가된 순서되로 진행됨.

 - 디폴트값은 0

 useWeakReference

 

 - 강참조(strong reference), 약참조(weak reference) 정함

 - 디폴트는 강참조.

 - 강참조는 가비지콜렉트 하는것을 막음. 약참조는 가비지콜렉트 하도록 내버려둠.

 - 클래스레벨 멤버 함수는 가비지콜렉션의 대상이 아님.    

   Class-level member functions are not subject to garbage collection, so you can set useWeakReference to true for class-level member functions without subjecting them to garbage collection. If you set useWeakReference to true for a listener that is a nested inner function, the function will be garbage-collected and no longer persistent. If you create references to the inner function (save it in another variable) then it is not garbage-collected and stays persistent.        

 

 

*도움이 되는 글

퍼플린님의 블로그 (Flex 이벤트의 전파단계와 버블링) http://rinn.kr/25

지돌스타님의 블로그(라이프사이클) http://blog.jidolstar.com/226

지돌스타님의 블로그(이벤트전파) http://blog.jidolstar.com/415

커스텀이벤트시 알아야할 사항 http://gogothing.tistory.com/51

지클짱께님의 블로그(Custom Event를 만들 때 clone() 함수를 override 해야하는 이유)  http://blog.naver.com/sinavari?Redirect=Log&logNo=40062521053

쫌조님의 블로그(Flex의 Single Thead와 비동기 프로그래밍 모델) http://blog.naver.com/PostView.nhn?blogId=jjoommnn&logNo=130050249083

AS3 Event http://bixworld.egloos.com/2149717

ActionScript 도움말 http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7e4f.html

AND