Essensial ActionScript 3.0(이하 EAS)책에 보면 Stage의 invalidate() 메소드와 Event.RENDER의 활용법이 나와있다.(한국어 판이 나올 예정이니 기대가 크다. ^^)

 

invalidate() 메소드는 Event.RENDER 이벤트를 발생해주는 역할을 한다. 이것을 적절하게 사용하면 화면갱신을 필요할 때만 적절하게 수행할 수 있기 때문에 애플리케이션의 전체적인 퍼포먼스를 향상할 수 있게 된다. Event.RENDER 이벤트는 invalidate() 메소드의 호출 외에도 MouseEvent, KeyboardEvent등의 updateAfterEvent() 메소드를 호출해도 발생한다.

 

EAS 23장에 Event.RENDER를 이용한 최적화에서 최종 예를 보면 다음과 같다.(올려도 되는건가??)

 

package {
 import flash.display.Shape;
 import flash.events.*;

 public class Ellipse extends Shape {
  private var w:Number;
  private var h:Number;
  private var changed:Boolean;

  public function Ellipse (width:Number, height:Number) {
   var stageDetector:StageDetector = new StageDetector(this);
   stageDetector.addEventListener(StageDetector.ADDED_TO_STAGE,
                  addedToStageListener);
   stageDetector.addEventListener(StageDetector.REMOVED_FROM_STAGE,
                  removedFromStageListener);

   w = width;
   h = height;

   setChanged( );
  }

  public function setWidth (newWidth:Number):void {
   w = newWidth;
   setChanged( );
  }

  public function getWidth ( ):Number {
   return w;
  }

  public function setHeight (newHeight:Number):void {
   h = newHeight;
   setChanged( );
  }

  public function getHeight ( ):Number {
   return h;
  }

  private function setChanged ( ):void {
   changed = true;
   if (stage != null) {
    stage.invalidate( );
   }
  }

  private function clearChanged ( ):void {
   changed = false;
  }

  protected function hasChanged ( ):Boolean {
   return changed;
  }

  private function addedToStageListener (e:Event):void {
   stage.addEventListener(Event.RENDER, renderListener);

   if (hasChanged( )) {
    stage.invalidate( );
   }
  }

  private function removedFromStageListener (e:Event):void {
   stage.addEventListener(Event.RENDER, renderListener);
  }

  private function renderListener (e:Event):void {
   if (hasChanged( )) {
    draw( );
   }
  }

  private function draw ( ):void {
   graphics.clear( );
   graphics.lineStyle(1);
   graphics.beginFill(0xFFFFFF, 1);
   graphics.drawEllipse(0, 0, w, h);
  }
 }
}

 

왜 저렇게 만들었는가 이유를 알고 싶다면 직접 분석하거나 EAS를 구입해보는 것이 좋겠다. 이것만은 기억하자 저렇게 만든 이유는 witdh, height가 바뀔때마다 draw() 메소드가 호출되어 쓸데없이 렌더링되는 것을 방지하기 위함이다.

 

본인의 생각에 위 코드의 최적화 부분에서 약간 문제점이 있다는 생각이 든다. 그것은 Ellipse객체가 Stage에 추가될 때 호출되는 addedToStageListener()내에 정의된 stage.addEventListener(Event.RENDER, renderListener)이 있는 부분이다.

 

이는 만약 100개의 Ellipse객체가 Stage에 추가된 경우 100개의 renderListener()이 등록된 것과 동일하다. Event.RENDER 이벤트가 발생할 때 renderListener()에서 hasChanged()메소드를 이용해 변경해야하는 경우에만 draw()를 호출한다고 하지만 그래도 여전히 100개의 renderListener() 이벤트 청취자 함수가 호출되는 것은 마찬가지이다.

 

렌더링하는 시점에만 stage.addEventListener(Event.RENDER, renderListener)로 renderListener() 메소드를 등록하고 renderListener()이 호출되면 stage.removeEventListener(Event.RENDER, renderListener)를 실행하면 쓸데없이 renderListener() 메소드가 호출되는 것을 막을 수 있지 않을까? 가령, 1개의 Ellipse객체만 다시 그려야하는 경우 그 객체의 renderListener() 메소드만 호출하면 되기 때문에 전체 퍼포먼스를 향상하는데 더 좋을 것이다. 그래서 본인은 위 Ellipse 클래스를 아래처럼 수정해봤다.

 

 

package {
 import flash.display.Shape;
 import flash.events.*;

 public class Ellipse extends Shape {
  private var w:Number;
  private var h:Number;
  private var changed:Boolean;

  public function Ellipse (width:Number, height:Number) {
   var stageDetector:StageDetector = new StageDetector(this);
   stageDetector.addEventListener(StageDetector.ADDED_TO_STAGE,
                  addedToStageListener);
   stageDetector.addEventListener(StageDetector.REMOVED_FROM_STAGE,
                  removedFromStageListener);

   w = width;
   h = height;

   setChanged( );
  }

  public function setWidth (newWidth:Number):void {
   w = newWidth;
   setChanged( );
  }

  public function getWidth ( ):Number {
   return w;
  }

  public function setHeight (newHeight:Number):void {
   h = newHeight;
   setChanged( );
  }

  public function getHeight ( ):Number {
   return h;
  }

  private function setChanged ( ):void {
   changed = true;
   if (stage != null) {
    stage.addEventListener(Event.RENDER, renderListener, false, 0, true);
    stage.invalidate( );
   }
  }

  private function clearChanged ( ):void {
   changed = false;
    stage.removeEventListener(Event.RENDER, renderListener);
  }

  protected function hasChanged ( ):Boolean {
   return changed;
  }

  private function addedToStageListener (e:Event):void {
   if (hasChanged( )) {
		stage.addEventListener(Event.RENDER, renderListener, false, 0, true);
    stage.invalidate( );
   }
  }

  private function removedFromStageListener (e:Event):void {
   clearChanged();
  }

  private function renderListener (e:Event):void {
   if (hasChanged( )) {
    clearChanged();
    draw( );
   }
  }

  private function draw ( ):void {
   graphics.clear( );
   graphics.lineStyle(1);
   graphics.beginFill(0xFFFFFF, 1);
   graphics.drawEllipse(0, 0, w, h);
  }
 }
}

 

자, 위처럼 하면 필요할때만 renderListener()를 호출하므로 더 최적화 되었다고 할 수 있겠다.

 

이 방식을 응용하여 Sprite를 확장해서 필요할 때만 렌더링해주는 Flex의 UIComponent와 같은 추상클래스(?)를 만들어주면 어떨까?

 

이름은 flash.display.UISprite로 하고 그에 대한 인터페이스는 flash.display.IUISprite로 정했다. UISprite에는 Flex의 UIComponent에서 제공하는 invalidateProperties(), invalidateDisplayObject()를 Event.RENDER를 이용해 흉내낸 클래스이다. Flex SDK를 활용하지 않고 개발할 때 이 클래스는 최적화된 Rendering을 하는데 도움을 줄 수 있을 것이다.

 

 

package flash.display
{
	import flash.display.Sprite;
	import flash.events.Event;

	/**
	 * ActionScript 3.0 기반으로 만드는 프로젝트의 시각화 객체를 사용해야할 경우 이것을 확장해서 사용하도록 한다.
* 사용법은 Flex의 UIComponent와 흡사하다. * * @author Yongho, Ji (jidolstar@gmail.com) * @since 2009.05.12 */ public class UISprite extends Sprite implements IUISprite { private var isInvalidateProperties:Boolean = true; private var isInvalidateDisplayObject:Boolean = true; private var isCreatedChildren:Boolean = false; private var isCreationComplete:Boolean = false; public function UISprite() { super(); this.addEventListener( Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true ); this.addEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true ); } /** * 고정적인 시각화 객체를 추가할 때 사용하는 함수로 Override 해서 사용한다. */ protected function createChildren():void { } /** * 속성이 변경되었을 때 처리하는 함수로 Override 해서 사용한다. */ protected function commitProperties():void { } /** * 시각화 객체가 새로 렌더링할때 사용하는 함수로 Overide해서 사용한다. */ protected function updateDisplayObject():void { } /** * 라이프 사이클이 모두 완료되면 이 함수가 호출된다. * 단 한번만 호출되는데 필요할 때 Override해서 사용하면 되겠다. */ protected function creationComplete():void { } /** * @inheritDoc */ public function validateProperties():void { isInvalidateProperties = true; commitProperties(); } /** * @inheritDoc */ public function validateDisplyObject():void { isInvalidateDisplayObject = true; updateDisplayObject(); } /** * @inheritDoc */ public function invalidateProperties():void { if( isInvalidateProperties ) return; isInvalidateProperties = true; invalidate(); } /** * @inheritDoc */ public function invalidateDisplayObject():void { if( isInvalidateDisplayObject ) return; isInvalidateDisplayObject = true; invalidate(); } /** * 무효화 */ protected function invalidate():void { if( stage != null ) { stage.addEventListener( Event.RENDER, onRender, false, 0, true ); stage.invalidate(); } } /** * Stage에 추가 되었을때 처리 */ private function onAddedToStage( event:Event ):void { //자식 추가, 처음에만 한번 호출됨 if( !isCreatedChildren ) { isCreatedChildren = true; createChildren(); } //무효화 처리 if( isInvalidateProperties || isInvalidateDisplayObject ) { invalidate(); } } /** * Stage에서 제거 되었을 때 처리 */ private function onRemovedFromStage( event:Event ):void { if( stage != null ) { stage.removeEventListener( Event.RENDER, onRender ); } } /** * Render 이벤트 처리 */ private function onRender( event:Event ):void { if( stage != null ) { stage.removeEventListener( Event.RENDER, onRender ); } //commit properties if( isInvalidateProperties ) { isInvalidateProperties = false; commitProperties(); } //update display object if( isInvalidateDisplayObject ) { isInvalidateDisplayObject = false; updateDisplayObject(); } //creation complete. call only once if( !isCreationComplete ) { isCreationComplete = true; creationComplete(); } } /** * @inheritDoc */ public function get childrenCreated():Boolean { return isCreatedChildren; } /** * @inheritDoc */ public function get creationCompleted():Boolean { return isCreationComplete; } /** * @private */ override public function set width(value:Number):void { super.width = value; invalidateDisplayObject(); } /** * @private */ override public function set height(value:Number):void { super.height = value; invalidateDisplayObject(); } /** * @private */ override public function set scaleX(value:Number):void { super.scaleX = value; invalidateDisplayObject(); } /** * @private */ override public function set scaleY(value:Number):void { super.scaleY = value; invalidateDisplayObject(); } /** * override해서 trace문을 대신 정의해서 사용한다. * * @example * * override protected function TRACE( ...args ):void * { * trace( prototype.constructor.toString(), args ); * } * */ protected function TRACE( ...args ):void { //trace( prototype.constructor.toString(), args ); } } }

 

UISprite는 검증되지 않았고 프로젝트에 적용하기 위해 테스트 단계에 있다.

 

전체 소스는 아래서 다운로드 받는다.

 

 

이 클래스에 대한 문제점 및 다른 의견이 있다면 언제든지 댓글 및 피드백을 걸어주었으면 한다.

+ Recent posts