Developer Book review 09.11.26
ㅎㅎ 드디어 손에 들어왔군요.
그들(?)의 최신작인 .fla2입니다.
당연하게도 웹에 공개된 것보단 훨씬 친절하고 자세한 내용을 다루고 있군요.
12월 5일에 긴자 애플샵에서 출판기념세미나 하는거 같은데 다녀올까말까 고민중입니다.
그나저나 거의 발매와 동시에 1쇄가 완판되었군요. 좋겠다..
일본이 이런 책이 완판되는 곳임을 고려했을때 일본에다가 책을 내볼까라는 생각도 해봅니다 ㅎㅎ
Archive for November 2009
ㅎㅎ 드디어 손에 들어왔군요.
그들(?)의 최신작인 .fla2입니다.
당연하게도 웹에 공개된 것보단 훨씬 친절하고 자세한 내용을 다루고 있군요.
12월 5일에 긴자 애플샵에서 출판기념세미나 하는거 같은데 다녀올까말까 고민중입니다.
그나저나 거의 발매와 동시에 1쇄가 완판되었군요. 좋겠다..
일본이 이런 책이 완판되는 곳임을 고려했을때 일본에다가 책을 내볼까라는 생각도 해봅니다 ㅎㅎ
as3에서 메모리릭의 가장 큰 주범은 뭐니뭐니 해도 이벤트 리스너입니다. 약한 참조가 실제로 동작하지 않기 때문에 일단 addEventListener를 했다손 치면 Dispatcher도 Listener도 전혀 GC가 안됩니다.
매번 꼼꼼하게 removeEventListener를 해주는게 정답입니다만 그게 어디 그렇게 쉽나요. 1회성 이벤트의 경우 사용 후 즉시 해제하는 경우가 많습니다. 또 그렇지 않더라도 조건을 걸어서 얼마든지 자신이 호출되는 시점에 제거하는 건 가능하겠죠. 지금 소개시켜드리는 간단한 코드는 리스너가 스스로를 해지할 수 있게 도와줍니다.
function listener( $e:Event ):void{
$e.target.removeEventListener( $e.type, arguments.callee, !$e.bubbles );
}
뭐 설명을 드릴게 딱히 없습니다. 이벤트 객체에는 이미 dispatcher가 누구인지, 어떤 event인지 버블단계인지 캡쳐단계인지에 대한 충분한 정보가 포함되어있고, 마지막 걸림돌은 함수 자신을 가리키는 포인터인데 this는 함수 입장에서 클로저를 가리키는데 반해 arguments.callee는 함수 자신을 가리키므로 removeEventListener를 하기 위한 모든 재료가 갖춰집니다.
즉시 해지를 하던 if문 등이 관여해서 조건을 충족할 때 해지되게 하던, 그건 그때그때 생각해볼 문제고 중요한 포인트는 리스너 스스로가 자신을 해지하는 능력을 갖게 만들어 개발자가 신경쓰지 않아도 메모리릭을 방지한다는 점이겠죠.
게다가 저 코드는 하나의 리스너를 여러개의 dispatcher에 등록해도 잘 작동하기 때문에 범용적인 코드로서 충분한 매력이 있습니다.
플래시에서 폰트를 embed하는 방법은 크게 세가지가 있습니다.
저도 이렇게 사용하고 있었는데 이번에 CFF문제를 당해 그으냥님의 도움으로 두가지를 더 알게 되었습니다.
이 두 가지 옵션 중에 embedAsCFF를 true 또는 false를 하여 CFF상태를 지정할 수 있게 됩니다.
어떠한 알고리즘은 유연하게 되면 될수록 불안정해지는 경향이 있습니다. 반대로 아주 한정적으로만 사용된다면 당연히 안정성은 높아집니다. 하지만 프레임웍이나 기반 클래스를 구축하는 경우에는 매우 광범위한 수비범위를 갖는 클래스를 작성하고 테스트를 충분히 하는 것으로 안정성을 확보하는 방식을 사용할 수 밖에 없습니다.
이런 경우에는 코드의 안정성보다 유연성에 중점을 두는 경우라 할 수 있는데, 일반적인 프로그래밍에서 반드시 지양해야 할 항목이기도 합니다. 단지 일부 기저 클래스에서는 유용한 경우가 있기 때문에 알아둘 필요가 있는 것이겠죠. 이번에 다룰 주제가 바로 이러한 극단적인 유연성을 추구하는 코드에 대한 얘기입니다.
함수의 호출은 ECMAscript기반 언어에서 두 가지 방식을 사용할 수 있습니다. 하나는 직접 호출이고 다른 방법은 위임 호출입니다.
직접 호출은 말 그대로 function(); 이렇게 직접 코드 상에서 함수를 호출하는 경우를 말합니다. 이렇게 호출되는 함수는 내부적으로 현재 구동적인 프로그램의 컨텍스트에 맞춰 함수에게 스택메모리와 환경을 조성하여 호출하게 됩니다.
하지만 함수가 일급객체인 언어에서는 함수에 대한 참조를 변수로 잡아둘 수 있기 때문에 함수호출이 아닌 함수 자체를 저장했다가 사용하는 것이 가능합니다. 아래와 같은 경우겠죠.
function test():void{
trace('test');
}
var delegate:Function;
delegate = test;
delegate();
왜 이렇게 할까요? 그것은 함수변수에 다양한 함수의 참조를 런타임에 할당하여 유연하게 사용하면서 알고리즘은 동일하게 delegate만 호출하기 위해서 입니다. 그러한 예는 아래와 같습니다.
function test1():void{
trace('test1');
}
function test2():void{
trace('test2');
}
var delegate:Function;
delegate = test1;
delegate(); // 'test1'
delegate = test2;
delegate(); // 'test2'
위의 예에서 호스트코드는 분명히 동일한 delegate를 호출했지만 결과는 할당된 함수에 따라 다르게 나타납니다. 이러한 식으로 몇 단계를 거치든 함수 자체를 호출하지 않고 함수의 참조를 통해 호출하는 행위를 1차적인 의미에서 위임호출이라 할 수 있습니다.
하지만 위임호출은 이것만이 아닙니다. Function객체는 내부적으로 apply와 call을 갖고 있는데 이 메서드를 통해서도 위임호출을 할 수 있습니다. 하지만 위의 함수참조를 이용한 호출과 apply를 이용한 호출은 목적이 매우 다릅니다.
함수의 참조를 이용하는 경우는 보통 (위의 예에서처럼) 호스트코드를 유지하면서 다양한 함수를 런타임에 할당하기 위해서입니다. 일종의 간이 전략패턴이라고도 할 수 있습니다.
하지만 apply를 사용하는 이유는 컨텍스트를 재할당하거나 인자를 동적으로 배치하기 위해서 입니다.
이 부분에 대한 이해를 위해서는 선행적으로 컨텍스트에 대한 학습이 필요합니다. 컨텍스트는 어도비 공식문서에도 나와있지만 그보단 제 블로그의 클로저에 대한 글이 더욱 자세하게 설명해드릴 겁니다.
apply메서드를 사용하면 함수자체를 호출할 때는 불가능한 일을 할 수 있습니다.
var a:int=30;
function test():void{
trace( a );
}
test();
위의 예를 실행하면 30을 반환합니다. test의 컨텍스트가 test를 둘러싼 객체로 인식되기 때문에 var a:int=30을 a의 대상으로 간주한 것이죠. 아래의 예에서는 그렇지 않습니다.
var a:int=30;
var b:Object = {a:50};
function test():void{
trace( a );
}
test.apply( b );
이렇게 전달하면 컨텍스트를 b로 옮긴 상태에서 함수의 값을 바인딩 합니다. b에서는 a가 50으로 선언되어있기 때문에 결과는 50이 됩니다.
이처럼 런타임에 강력하게 사용할 수 있는 apply는 그에 상응하는 약점을 갖고 있습니다.
위의 함수 위임호출을 이용하면 어떠한 함수라도 간접적으로 자유롭게 호출할 수 있습니다. 하지만 ECMAscript에는 특이한 함수가 존재하는데 바로 생성자입니다. 생성자를 호출하는 경우는 다른 함수 호출과 구분하기 위해 반드시 new를 사용합니다. as3에서 new란 사실 다른 OOP언어에서 의미하는 new와는 조금 다릅니다만 이건 또 기회가 되는 나중에 설명하기로 하고 일단 new를 사용하여 함수를 호출하면 생성자 호출이란 행위가 일어난다는 점을 생각해야 합니다.
생성자 자체를 위임하는 것은 Class형을 통해 가능하지만, 생성자 호출은 불행히도 apply가 되지 않기 때문에 다양한 생성자를 런타임에 바인딩하는 것이 일반적으로는 불가능합니다. 오직 인자가 없는 경우에만 가능합니다. 이러한 생성자 위임의 샘플은 아래와 같습니다.
var delegate:Class = flash.display.Sprite; var s:Sprite; s = new delegate() as Sprite;
하지만 인자가 있는 경우엔 쉽지 않습니다. 인자를 하드코딩 해야 하기 때문이죠. 따라서 동적인 처리를 하려면 인자의 갯수에 따라 분기를 할 수 밖에 없습니다. 일반적으로 생성자의 인자 갯수가 그렇게 많지 않다는 점을 고려하면 적당한 수준에서 정리할 수 있습니다.
여태까지의 모든 내용을 바탕으로 getInstance라는 범용함수를 구성하면 다음과 같습니다.
function getInstance( $class:Class, $param:Array = null ):*{
var cls:Class;
cls = $class;
if( $param ){
switch( $param.length ){
case 1: return new cls( $param[0] );
case 2: return new cls( $param[0], $param[1] );
case 3: return new cls( $param[0], $param[1], $param[2] );
case 4: return new cls( $param[0], $param[1], $param[2], $param[3] );
case 5: return new cls( $param[0], $param[1], $param[2], $param[3], $param[4] );
case 6: return new cls( $param[0], $param[1], $param[2], $param[3], $param[4], $param[5] );
case 7: return new cls( $param[0], $param[1], $param[2], $param[3], $param[4], $param[5], $param[6] );
case 8: return new cls( $param[0], $param[1], $param[2], $param[3], $param[4], $param[5], $param[6], $param[7] );
case 9: return new cls( $param[0], $param[1], $param[2], $param[3], $param[4], $param[5], $param[6], $param[7], $param[8] );
case 10: return new cls( $param[0], $param[1], $param[2], $param[3], $param[4], $param[5], $param[6], $param[7], $param[8], $param[9] );
case 0:default: return new cls;
}
}else{
return new cls;
}
}
이를 사용하면 직접 생성하지 않고 함수를 통한 위임생성이 가능합니다.
// new Bitmap( new BitmapData(100,100), 'auto', true ) 와 동일 getInstance( Bitmap, [new BitmapData(100,100), 'auto', true ] );
여지껏 알아본 위임생성과 위임호출은 일반적인 경우를 위해 사용하는 것이 아닙니다. 대표적으로 Loader를 통해 swf로 부터 동적으로 클래스를 읽어 들이는 경우를 예로 들 수 있습니다. 이런 경우는 아래와 같은 코드도 보다 편리하게 사용할 수 있습니다.
getInstance( _loader.contentLoaderInfo.applicationDomain( 'test.testClass' ), [arg1, arg2] );
결론적으로 컴파일타임에 알 수 없는 런타임 로딩 클래스들을 편리하고 안정적이며 일괄로 처리하기 위해 익혀야 하는 부분이라 할 수 있습니다.