[AS3.0 소스 공유] 이벤트 터질 때, 핸들러에 이벤트와 함께 인자값을 전달하는 EventProxy 유틸 클래스
Flash Platform/FLASH / 2011/02/17 18:05
가끔 이벤트 핸들러에 인자 값을 전달한다던지, 특정 횟수 만큼 호출되고 나면, 삭제를 해야 한다던지 할 경우가 있지요..
그런 이유로 다들 EventUtil 하나 정도는 가지고 계실 것 같은데요 ^^;
잉여 돋는 시간에 프레임워크를 만들고 있는데, 그 중 일부인 소스 공유합니다.
사용 방법은 주석에 써놨습니다~ ^^
다들 즐거운 하루 되세용~
그런 이유로 다들 EventUtil 하나 정도는 가지고 계실 것 같은데요 ^^;
잉여 돋는 시간에 프레임워크를 만들고 있는데, 그 중 일부인 소스 공유합니다.
사용 방법은 주석에 써놨습니다~ ^^
다들 즐거운 하루 되세용~
package actionwire.util
{
import flash.events.Event;
import flash.events.IEventDispatcher;
import flash.utils.Dictionary;
/**
*
* @author lovedev
* @example 사용 예
*
* var sp:Sprite = new Sprite();
* EventProxy.addEventListener(sp, Event.SELECT_ALL, function($e:Event, $test:String):void
* {
* trace($test);
* },1,"testMessage AAA");
*
* var fn:Function = function($e:Event, $test:String):void
* {
* trace($test);
* };
*
* EventProxy.addEventListener(sp, Event.SELECT_ALL, fn,0,"TestMessage BBB");
*
* sp.dispatchEvent(new Event(Event.SELECT_ALL));
* EventProxy.removeEventListener(sp, Event.SELECT_ALL, fn);
* sp.dispatchEvent(new Event(Event.SELECT_ALL));
*
* //output
* //testMessage AAA
* //TestMessage BBB
*
* -----------------------------------------------------------------------------------
* //이벤트 dispatch는 2번 되었지만
* //테스트 메시지 AAA를 찍는 핸들러는 1회 호출 후 자동 remove 되었으며,
* //테스트 메시지 BBB를 찍는 핸들러는 강제 remove되어 핸들러가 호출되지 않는다.
* -----------------------------------------------------------------------------------
*
*
*/
public class EventProxy
{
private static var _eventList:Dictionary = new Dictionary();
/**
* 이벤트 발생이 가능한 객체에 이벤트를 등록합니다.
* @param $target 이벤트 발생 객체
* @param $eventType 이벤트 타입
* @param $listener 이벤트 핸들러
* @param $limitCount
* 호출 제한 카운트 지정 0일 경우 무제한 지정되었을 경우,
* 지정된 수만큼 반복후 remove
* @param $rest 핸들러에 전달될 인자
*
*/
public static function addEventListener( $target:IEventDispatcher,
$eventType:String,
$listener:Function,
$limitCount:uint = 0,
...$rest):void
{
if(!_eventList[$target])
_eventList[$target] = {};
if(!_eventList[$target][$eventType])
_eventList[$target][$eventType] = [];
_eventList[$target][$eventType].push({
"listener" : $listener,
"args" : $rest,
"limitCount" : $limitCount,
"executeCount" : 0
});
if(_eventList[$target][$eventType].length == 1)
$target.addEventListener($eventType, onProxyDispatchEvent);
}
/**
*
* @param $e
*
*/
private static function onProxyDispatchEvent($e:Event):void
{
var target:IEventDispatcher = $e.currentTarget as IEventDispatcher;
var eventType:String = $e.type;
if(_eventList[target].hasOwnProperty(eventType) &&
_eventList[target][eventType].length > 0)
{
var listener:Function, args:Array,
limitCount:uint, idx:String, eventInfo:Object;
var removableListener:Array = [];
for(idx in _eventList[target][eventType])
{
eventInfo = _eventList[target][eventType][idx];
listener = eventInfo.listener;
args = eventInfo.args;
if(!(args[0] is Event)) args.unshift($e);
listener.apply(target, args);
eventInfo.executeCount++;
if(eventInfo.limitCount > 0
&& eventInfo.limitCount <= eventInfo.executeCount)
{
removableListener.push(eventInfo);
}
}
for each (eventInfo in removableListener)
removeEventListener(target, eventType, eventInfo.listener);
listener = null;
args = null;
idx = null;
eventInfo = null;
removableListener = null;
}
}
/**
* EventProxy를 통해 생성된 이벤트를 제거합니다.
* @param $target
* @param $eventType
* @param $listener
*
*/
public static function removeEventListener( $target:IEventDispatcher,
$eventType:String = null,
$listener:Function = null):void
{
//Case 1
if($eventType === null && $listener === null)
{
for(var eventType:String in _eventList[$target])
{
$target.removeEventListener(eventType,
onProxyDispatchEvent);
}
delete _eventList[$target];
return;
}
//Case 2
if($eventType !== null && $listener === null
&& _eventList[$target].hasOwnProperty($eventType))
{
$target.removeEventListener($eventType,
onProxyDispatchEvent);
delete _eventList[$target][$eventType];
return;
}
//Case 3
if($eventType !== null && $listener !== null
&& _eventList[$target].hasOwnProperty($eventType))
{
for(var idx:String in _eventList[$target][$eventType])
{
if(_eventList[$target][$eventType][idx].listener === $listener)
{
_eventList[$target][$eventType][idx] = null;
_eventList[$target][$eventType].splice(int(idx), 1);
break;
}
}
if(_eventList[$target][$eventType].length == 0)
{
$target.removeEventListener($eventType,
onProxyDispatchEvent);
delete _eventList[$target][$eventType];
}
}
}
}
}


댓글을 달아 주세요
처음으로 댓글을 남겨봅니다(처음부터 이런 글이냐..)
위에서 언급하신 것처럼 이벤트 관련으로 다들 비슷한 생각들을 해보셨을거라 대동소이한 소스를 갖고 있어요 ^^
단지 개인적인 의견을 말씀드리자면(이제부터 시작..)
매번 unshift로 이벤트를 삽입하는 건 좀 지양해야할 거 같고,
실무상 rest 외에도 외부 파일로부터의 입력이나 기존 배열로부터의 입력도 잦기 때문에 배열을 인자로 받는 시그니처도 같이 제공해 주시면 더 좋을거 같고,
hasown도 느리니까 안쓰고 걍 대괄호 두 번으로 대체했으면 좋겠고,
길이체크도 리무브로직을 믿으신다면 안했으면 좋겠지만..
이런 건 다 게임만드는 입장이라 기저프레임웍이 호출되는 횟수를 고려했을 때 성능에 대한 주관적인 견해이고,
진짜 의견은..
매번 커스텀 정보를 오브젝트를 생성해서 넣을거면 걍 실드클래스를 자료전용으로 선언해서 차라리 그 클래스의 인스턴스를 생성하고 디스패치시에도 해당 인스턴스의 메소드를 호출하도록 중앙 스태틱에서 알고리즘을 이사시킬 것과,
이를 통해 배열을 각 딕셔너리의 컨테이너로 쓰시던걸 벡터로 전환하심이 어떨까하는거죠.
결국 분명히 더 추가되거든요.
지금 도입하신 리미트체크기능 외에도 수 많은 커스텀 확장 기능이....
그 때마다 이벤트 처리기 본체를 건드리는 것보단 역시 개별 이벤트보관클래스를 수정하는 편이 안전하니까..^^
머 개인적인 사견이었습니당.
와~~ 엄청난!! 댓글이어요~~
이건 프레임워크의 일부 유틸이에요. ^^
잉여 시간에 만든 건데. 이것의 포인트는
dispatch를 Reflection시킨다기 보다는
eventType이 같을 경우 addEventListener는 1번밖에 안된다는 것이겠지요 ^^; 물론 이벤트 전파 과정에 대한 생리 주기를 제한적으로 타는 것이 단점이에요.
priority같은 우선 순위나 버블 개념이겠지요..
이 외에 Vector로 할 경우, Key 인덱싱 시간이 더 들 것 같습니다. 배열의 단점이잖아요.. 또한 9기준으로 했을 때, 호환성 측면에서는 Vector는 안쓰는게;;
말씀하신 것처럼, Custom Object보다 클래스로 가는게 빠르고 좋아요~ ^^)/ 맞아요~
그냥 30분 정도 생각하면서 프로토타입 삼아 만든거라.. 세밀한 부분까지 적용하지 않은 것으로 받아 들여 주세오.. 무섭네요~ ^^;;;
rest외 배열로 인자를 받는 거 좋네요 ^^);;
Dictionary로 리스트를 잡은 것은 외부에서 GC되어 객체가 사라질 경우, 이벤트의 키값도 저절로 사라지도록 한 거라 봐주셔요~
Object를 키로 삼기 좋아서요.. 물론 Dictionary객체가 느리긴 하죠. ^^
첨부적으로 말씀 드리지만,
이벤트로직에 의한 통신으로 프레임워크는 움직이지 않습니다. 프레임워크는 프로토콜을 설계해야지요~
이벤트로 커뮤니케이션을 제어하는 것은 느리고 한계도 많아요..
이벤트보다 더 LOW레벨에서 지정된 프로토콜로 통신을 하게 됩니다.
그냥.. 간단히 프레임워크에서 이벤트를 써야할 때, Refelection하려고 유틸성으로 넣은 거에요~
사려깊은 댓글 무한 감사드립니다!!
if(!_eventList[$target][$eventType]){
_eventList[$target][$eventType] = [];
벡터를 도입하자는건 이부분의 배열로 중앙컨테이너인 딕셔너리를 대체하자는건 아니었습니다 ^^;
참고로 혹시 각 인스턴스가 브레이크로 찍어보면 @@로 시작하는 유일한 식별키를 갖고 있잖아요. 그것좀 추출하는 메서드가 있을까요 ^^; 모든 as3인의 열망 객체 id얻기를 성공시켰나 궁금합니다.
프로파일러는 만들어 봤는데.. 객체의 고유 아이디 찾아 본다는 생각은 안해봤네요..

Sampler로 얻어진 ID가 객체의 원래 아이디로 알고 있어서 이 부분을 테스트 해봤더니.. 아니더군요.
그래서 수단과 방법을 가리지 않고 아이디를 알아내긴 했는데요..
근데 이게 왜 필요한건지가 좀 ^^;;
객체 비교할 때 속도 측면?
바보 된거 같아여;;;
어디에 필요한지 알려 주시면~ 포스팅 하겠습니다. ^^;(용도가 궁금해서요;;
정답은 아니겠지만.. 찾아지긴 하네요..
원래 llvm이 가상머신에 개별 힙메모리 에뮬레이션으로 객체를 올릴때 숫자로 고유한 키를 부여해요. 자바에서는 최상위의 Object에 hash를 얻을수 있게 되어있잖아요.
만약 디버깅시에 찍히는 고유한 인스턴스 id를 알 수 있으면 객체를 키로 잡는 컨테이너로 더이상 Dictionary를 쓸 필요가 없이 배열이나 벡터등의 숫자 인덱스 기반으로 이사갈 수 있게 되잖아요. 그 외에도 인스턴스 자체를 식별한 전역 키가 있으니까 객체 레퍼런스를 인자로 보내지 않고 프리머티브를 보낼 수도 있죠. 예를들어
function setPosition( $target:DisplayObject, $x:Number, $y:Number ):void
이런 함수를 생각해보면 만약 인스턴스 고유키를 알아낼 수 있다면,
function setPosition( $targetId:String, $x:Number, $y:Number ):void{
var target:DisplayObject = getObjectById( $targetId );
...
이렇게 바꿀 수 있으니까 외부 설정으로 빼기도 좋고, 의존성도 현저하게 낮아지는 효과가...
물론 Dictionary의 경우도 대부분 이용목적이 객체 자체를 키로 잡기 위해서인데 이게 결국 내부적으로는 객체의 해쉬키를 문자열로 해서 키로잡도록 어셈블리되거든요.
var s:Shape = new Shape
var d:Dictionary = new Dictionary;
d[s] = 3;
이렇게 되면 객체키를 잡는건 유저가 그렇게 느끼는거고, 사실은 s의 해쉬키를 문자열로 해서 Dictionary가 잡아준거잖아요. 짜피 고유한 숫자아이디를 얻을 수 있다면
var s:Shape = new Shape
var v:Vector.<Shape> = new Vector.<Shape>();
v[getHashKey( s )] = 3;
이렇게 바꿀 수 있으니까..일단 객체키를 잡으면 그게 안되는 언어도 많아서 크로스하게 소스를 쓸 수도 없고 나쁘다는..
글 올렸습니다~ ^^
우오~ -0- 이렇게 이벤트 관리하는 법도 있군요. 라고 리플 달려고 보니 이건 뭐... 신들의 대화;;;;;;
글 하나하나 모두 엄청 나네요. 본문의 소스를 30분 만에 작성하신 lovedev 님 도 정말 대단하십니다. 역시 ACC ^^
구현 방법적인 면은 계속 디벨롭 될 수 있겠죠. 그런걸 떠나서 아이디어 자체가 너무 신기합니다.
생각도 못해봤다는.. 큰 영감 받고 갑니다.
네네 그대로 쓰지 말고!! 보완해서 쓰셔요!! 그래야 발전의 발전을 거듭할 수 있고, 공개하는 의미도 있을 것 같아요! 좋게 봐주셔서 그저 감사할 따름이어요~ ^^;
개선하면 트랙백이라도 걸어 주세요~ ^^
감사합니다!