어플리케이션을 제작하다 보면 거의 대부분이 사용자 이벤트와 시스템 이벤트로 돌아가도 과언이 아닌데, 이렇게 중요한 이벤트에 대해서 쓸 줄만 알았지 정작 어떻게 돌아가는지 정확히 아는 것에는 게을렀다. 책에도 개념적으로 대충 흘리며 지나가고 뭔가 알듯 말듯 머리 속에서 혼란만 와서 대충 넘어갔는데 이번 주 빡시게 이벤트에 대한 놈을 제대로 잡아 갈려고 한다. 더 이상 이리 저리 휘둘리기 싫다면 매번 쓰는 이벤트에 대한 이해를 이번 기회를 통해 함께 알아가도록 하자.
이벤트의 3대 요소
- Event(event source):
이건 말 그대로 이벤트가 발생할 경우 그 이벤트라는 사건 자체다. 여기에는 어떤 이벤트(이벤트 타입)인지와 그 이벤트를 발생시킨 target과 currenTarget에 대한 속성 정보를 가지고 있다.
target: event target은 이벤트 행위가 이루어진 그 자체이다.
currentTarget: dispatcher 그 자체 이다.
- Dispatcher(event object, subject, target):
(정말 책을 보게 되면 좌절감을 맛보는 것이 용어의 혼란이다. 물론 다른 책에서 다른 용어를 쓴다면 그나마 이해를 한다지만 어떤 책들은 같은 책에서 같은 용어를 다른 말로 해석해 버리곤 하는데, 정말 그 분들에게 이왕 수고하시는 거 용어 통일도 같이 부탁 드리면서 이 말도 많고 탈도 많은 이벤트 객체에 대해서 알아 보기로 한다.)
말하다 보니 말이 길어 졌는데 이벤트 객체, 이건 말 그대로 이벤트를 발생 시킬 수 있는 바로 그 객체이다. 버튼이 될 수도 있고 어떤 비트맵 이미지가 될 수도 있고 display object부터 시스템적인 무형의 객체(ex.데이터 로드 완료 이벤트 처리 관련)가 될 수도 있다는 것이다.
-Listener(Event Listener(함수)):
이벤트가 발생 할 경우 호출되는 처리문(함수)이다.
이렇게 이벤트를 쓰기 위한 재료가 준비되었다면 이제는 이벤트를 연결(등록)하고 또 썼다면 제거하는 부분에 대해서 알아본다.
이벤트 연결(등록)과 제거(메모리 관리)
이벤트 등록은 아주 쉽다.
Event나 Event를 더욱 명세화한 subEvent를 상속 받으면 addEventListener를 통하여 쉽게 event를 해당 dispatcher에 연결 시킬 수가 있는 것이다.
import flash.events.*;
…
dispatcher.addEventListener(Event, Listener, UseCapture:Boolean=false, Priority:Number=0, useWeakReference:Boolean=false);
매개변수 설명
Event
Listener
UseCapture - Capture 단계에서 Event를 발생 시킴
Priority – 원래는 Listener를 등록한 순서부터 이벤트 처리가 되지만 만약 여기에 0이상의 더 높은 숫자 값을 부여하면 우선 순위를 줄 수가 있다.
UseWeakReference – Listener가 dispatcher와 유기적으로 결합되어 참조 값을 가지게 함으로써 만약 dispatcher가 제거가 되면 해당 Listener 또한 Garbage Collector가 참조 값에 대한 제거 대상에 포함을 시킨다.(그렇다고 이 부분을 true를 준다고 해서 참조 값이 바로 제거 된다는 것은 아니다.)
여기서 하나 알고 간다면 반드시 쓴 것은 반드시 제거 해주는 것이 습관에 좋은 습관이다. 결국 Listener를 등록하고 참조 값을 가지게 했다면 당연히 해당 객체가 제거 된다면 Listener 또한 제거 해줘야 하는 것이다.
그런데 만약에 객체를 제거하고 Listener에 대한 참조 값을 제거 한다면 그 Listener의 참조 값은 아이를 잃어버리는 꼴이 된다. 결국 그 아이는 길 잃은 고아가 되고 존재는 하지만 찾을 수 없는 존재가 되기 때문에 결국 memory leak 현상이 발생 할 것이다.(액션 스크립트의 대부분의 memoory leak 현상은 바로 listener에 대한 경우가 대부분이다.)
따라서 제거는 필요 없는 상황이 발생할 경우 반드시 해줘야 한다.
dispatcher.removeEventListener(Event, Listener);
또한 중요한 것은 만약에 useCapture를 true를 해줬을 경우 이벤트 listener 제거에도 true로 해줘야 한다는 것이다. 이것은 listener 리스트에 capture단계에 발생 시킬 listener 참조 값과 bubbling단계에 발생 시킬 참조 값이 각자 따로 저장된다는 것을 알 수 있다.
자 그럼 이제 본격적으로 머리에 혼란을 야기 시키는 event trigger가 어떻게 이루어 지는지 알아보겠다.
이벤트는 크게 2단계의 과정을 거쳐 listener를 호출한다.
1.capure단계
--> 이 단계 사이에 target이라는 단계를 포함 시키기도 하지만 어떤 흐름이라기 보단 그 순간이라 정의 하면 될 듯하다.
2.bubbling단계
-capture단계는 상위 stage부터 해당 이벤트가 등록된 dispatcher까지 도달하기 까지 event를 전달해주는 단계라 생각하면 된다. 즉 그 말은 event란 발생이 되는 순간 부터 그 이벤트를 받을 주인을 찾아간다고 생각하면 된다.
-event를 전달 받은 dispatcher는 자신이 이벤트를 받을 뿐더러 해당 dispatcher의 자식 객체에게도 event를 전달해주는 좋은 짓(?)을 해준다.그렇게 되면 그 이벤트를 받는 대상이 되는 객체는 listener 를 호출할 수 있는 것이다.
-이제 마지막으로 해당 target이 이벤트를 잘 받았다는 것을 다시 dispatcher에게 잘 받았다고 보고가 올라오고 최종 stage까지 event를 잘 받았다는 전달이 올라 갈 것이다. 이것이 bubbling단계이다. 이 bubbling단계에서 dispatcher는 자신의 listener 를 호출하게 된다.
이제 헷갈리기 시작할 것 입니다. 이건 뭐라 말로 설명하기가 엄청 힘들다는 거죠. 위의 단계에 대해서 파악하고자 한다면 테스트를 많이 거쳐봐야 할 것입니다. 그럼 이제 하나 하나 분리를 해 나가면서 정리를 해 나가 보겠습니다.
우선 일반적인 이벤트는 bubbling 이벤트라 합니다. 즉 bubbling단계까지 모든 trigger를 완료한다는 것이지요. 하지만 그 중에는 Non bubbling 이벤트가 있기도 합니다.이 이벤트들은 bubbling단계를 수행 안 한다는 것입니다. 마지막으로 또 targetting만을 가지고 있는 이벤트가 있습니다.
와~ 정말 예외 또한 많아 보이는 이벤트 관련이군요. ^^ 여기서 이건 그렇다 치고 넘어 갑니다.
이제 위에서 잠깐 설명한 addEventListener에서 3번째 매개변수의 쓰임새를 살펴 볼 것 입니다. “useCapture”
딱 단어에서도 설명이 가죠?!~ capture 단계를 사용할 것인가?--->이건 다시 말해 capture 단계에서 listener 를 호출할 것인가 입니다.---> 이 말은 다시 말해서 대부분의 이벤트는 targetting이나 bubbling 단계에서 보여 준다는 것 입니다.
이제 서술형으로 예를 들어 보겠습니다.
1. 무대 위에 박스가 추가 되어 있습니다. 그 박스 속에는 사과가 들어가 있습니다. 이제 속으로 생각 합니다. 난 사과에게 말을 걸어 볼 꺼야 하고 박스에게 미리 알려 줍니다.(이벤트 등록)
2. 자 이제 무대에 사과에게 말을 건다고 소리 지릅니다.(이벤트 발생)
3. 무대에 울려 퍼진 소리는 박스가 듣게 됩니다.(capture 단계)
4. 박스는 순간 놀래서 자신의 안에 있는 사과에게도 말을 전달해 줍니다.(targetting)
5. 박스는 근데 이제 서야 저에게 나 사과가 너의 말을 들었다는 것을 말해 주네요. 무슨 군대도 아니고 선 조치 후보고 해주는 박스 상병이네요.(bubbling단계)
이렇게 이야기는 끝이 납니다. 무슨 이야기 인지 도통 모르시겠나요? ^^ 쿨럭
그렇다면 이제 코드 예제를 통해 살펴보죠.
package
{
import flash.display.Sprite;
import flash.events.*;
public class Events extends Sprite
{
public var _box:Sprite;
public var _apple:Sprite;
public function Events()
{
trace("constructor");
_box=new Sprite();
_box.graphics.lineStyle(0,0,0);
_box.graphics.beginFill(0xf45d43);
_box.graphics.drawRect(0,0,100,100);
_box.graphics.endFill();
_box.x=100;
_box.y=100;
addChild(_box);
_apple=new Sprite();
_apple.graphics.lineStyle(0,0,0);
_apple.graphics.beginFill(0xFFFFFF);
_apple.graphics.drawCircle(0,0,20);
_apple.graphics.endFill();
_apple.x=50;
_apple.y=50;
_box.addChild(_apple);
//_box.addEventListener(MouseEvent.CLICK,onClick,true);
_box.addEventListener(MouseEvent.CLICK,onClick,false);//useCapture를 false로 넣었다는 것은 이벤트를 자식 에게 전달도 할 뿐 만 아니라(targetting) box에게도 전달한 다는 것이다.(bubbling단계)
}
public function onClick(evt:MouseEvent):void
{
trace("Event...Access");
evt.target.x=Math.random()*20;
evt.target.y=Math.random()*20;
var phase:String;
switch(evt.eventPhase){
case EventPhase.CAPTURING_PHASE:
phase="Capture";
break;
case EventPhase.AT_TARGET:
phase="Target";
break;
case EventPhase.BUBBLING_PHASE:
phase="Bubbling";
break;
}
trace("Current event phase: "+phase);
}
}
}
디버깅을 해보자.
박스를 선택하면 target단계에서 이벤트가 발생 됨을 알 수 있다. dispatcher에 capture 단계를 거쳐 targetting까지 본인 스스로를 지정하고 이벤트가 발생됨을 알 수 있다. 즉 dispatcher와 target이 일치하는 순간이다.
사과를 클릭하면 bubbling단계에서 이벤트가 옴을 알 수 있다. 이것은 dispatcher가 capture 단계에 와서 사과를 targetting하고 버블 단계에서 사과의 움직임을 실행시켜 준 것을 알 수 있다.
자 그렇다면 useCapture를 true로 변경해보자.
그러면 박스는 capture 단계에서 이벤트를 발생시키지만 본인 스스로 targetting이 안되기 때문에 이벤트 전달을 잡을 수 없고 그냥 흘러가게 된다.
사과를 클릭하게 되면 capture 단계에서 이벤트가 발생 되었고 targetting은 사과를 선택해 주면서 사과는 움직일 수 있을 것이다.
이제 대충 좀 감 좀 잡으셔야 한다.
자 마지막으로 useCapture를 true,false로 설정하여 2개를 등록해 보자.
박스를 선택해 보면 마찬가지로 target단계에서 이벤트를 잡아준다.
사과를 선택해 보자. 아 둘다 움직이면서 capture 와 bubbling 단계에서 이벤트를 잡아 가는 것을 확인 할 수 있었다.
capture 단계에선 물론 사과가 그 이벤트를 잡았을 것이고 돌아오는 길에 bubbling 단계에서 박스가 이벤트를 잡았을 거라고 이제 말할 수 있다.
이제 감이 좀 팍팍 오시죠? 이해가 안 가면 갈 때까지 여러 방면으로 테스트 해 보시길 바랍니다. 정말 이벤트 trigger만 제대로 알아도 이젠 숲이 좀 보일 것 입니다. 그렇다고 뭐 다 안건 아니지만 ^^; 아무튼 여러분들도 하루 빨리 이벤트에 대한 정확한 개념을 알아 가시길 바라겠습니다. 휴~ 쓰다 보니 좀 길어 졌네요. 아 원래 긴 포스팅은 자제 해야 하는데 워낙에 중요하고 간단하게 설명할 수 없는 지라…





