Pull to refresh

Снова сигналы

Reading time4 min
Views4.7K
Перевод статьи Aiden Tailor о сигналах, которые мигрировали на ActionScript из C# благодаря усилиям Rober Penner. Это не просто перевод, а шпаргалка — выжимка статьи. Без воды, ничего лишнего, чтобы даже самый ленивый мог пробежать глазами. Также произведены тесты производительности. Пост не только для AS3 гуру, но и и для всех, кого волнуют вопросы повышения качества кода, так как содержит описание одной из реализаций шаблона проектирования Observer. А вот видеоурок.

По своей сути Сигнал — это отдельной менеджер событий со своим массивом подписчиков. Основная идея — уйти от строковых сравнений, используемых в EventDispatcher и порождения набора Event-ов. Исходники as3 библиотеки можно забрать из GIT-репозитория тут.

Signal — основной сигнал.

  package insideria.basicSignal  {
     import org.osflash.signals.Signal;
   
     public class AlarmClock  {
        public var alarm:Signal;
    
        public function AlarmClock() {
           alarm = new Signal(); // создаем инстанс
         }
        
        public function ring():void {
           alarm.dispatch(); // вещаем сигнал (событие)
         }
      }
  }

  package insideria.basicSignal  {
     import flash.display.Sprite;
   
     public class WakeUp extends Sprite {
        private var alarmClock:AlarmClock;
    
        public function WakeUp() {
           alarmClock = new AlarmClock();
           alarmClock.alarm.add(onRing); // вешаем листенер
           alarmClock.ring();
         }
    
        private function onRing():void { // обработчик
           trace("Wake up!");
         }
      }
  }
  


Передача параметров происходит перечислением типов в конструкторе сигнала:

  package insideria.basicSignalArguments {
     import org.osflash.signals.Signal;
   
     public class AlarmClock {
        public var alarm:Signal;
    
        public function AlarmClock() {
           alarm = new Signal(String); // через запятую типы параметров
         }
        
        public function ring():void {
           alarm.dispatch("9 AM"); // бродкаст
         }
      }
  }

  package insideria.basicSignalArguments {
     import flash.display.Sprite;
   
     public class WakeUp extends Sprite {
        private var alarmClock:AlarmClock;
    
        public function WakeUp() {
           alarmClock = new AlarmClock();
           alarmClock.alarm.add(onRing);
           alarmClock.ring();
         }
    
        private function onRing(time:String):void { //параметры должны быть того-же типа, что и в конструкторе сигнала
           trace("Wake up! It's already " + time);
         }
      }
  }
  


Garbage Collector съедает инстансы, если отписаться (проверил лично):

  alarmClock.alarm.remove(onRing); // отписаться
  alarmClock.alarm.addOnce(onRing); // отписывается после срабатывания, полезно, например, для Event.ADDED_TO_STAGE
  alarmClock.alarm.removeAll(); // отписать все слушатели
  


DeluxeSignal — роскошный (расширенный) сигнал позволяет получить доступ к владельцу сигнала и к самому сигналу.
Он может передавать в листенер как GenericEvent (общее событие), так и кастомные параметры (как базовый сигнал). Использовать его — иногда непозволительная роскошь (см. тесты производительности в конце поста)

  package insideria.deluxeSignal {
     import org.osflash.signals.DeluxeSignal;
     import org.osflash.signals.events.GenericEvent;
   
     public class AlarmClock {
        public var alarm:DeluxeSignal;
        public var message:String;
    
        public function AlarmClock() {
           alarm = new DeluxeSignal(this); //передается ссылка на target объект
           message = "This is a message from our AlarmClock";
         }
        
        public function ring():void {
           alarm.dispatch(new GenericEvent()); // инстанциируется GenericEvent
         }
      }
  }

  package insideria.deluxeSignal {
     import org.osflash.signals.events.GenericEvent;
     import flash.display.Sprite;
   
     public class WakeUp extends Sprite {
        private var alarmClock:AlarmClock;
    
        public function WakeUp() {
           alarmClock = new AlarmClock();
           alarmClock.alarm.add(onRing);
           alarmClock.ring();
         }
    
        private function onRing(event:GenericEvent):void {
           trace(event.target); // target, который передавался в конструкторе сигнала
           trace(event.signal); // сигнал
           trace(event.target.message);
         }
      }
  }
  


NativeSignal — нативный сигнал. Служит для конвертирования родных ActionScript событий в сигналы.
Пример:
  package insideria.nativeSignals {
     import org.osflash.signals.natives.NativeSignal;
   
     import flash.display.Sprite;
     import flash.events.Event;
   
     public class AddToStage extends Sprite {
        public function AddToStage() {
           var added:NativeSignal = new NativeSignal(this, Event.ADDED_TO_STAGE, Event); // способ полностью уйти от использования Event'ов
           added.addOnce(onAdded);
         }
    
        private function onAdded(event:Event):void  {
           graphics.beginFill(0xCCCCCC);
           graphics.drawRect(0, 0, 100, 100);
           graphics.endFill();
         }
      }
  }
  


Производительность:

— профайл показал, что Signal в среднем отжирает дополнительно 4 кб оперативки на инстанс по сравнению в EventDispatcher
— Signal не более, чем в 2 раза медленнее, чем EventDispatcher
— Deluxe Signal примерно в 4,5 раза медленнее, чем EventDispatcher (теперь ясно, почему такое называние)
— NativeSignal — не тестировался, но анализ кода показал, что производительность будет примерно такая-же, как и у Signal

Код на котором производился тест скорости выполнения:

  public class Main extends Sprite {
      
    private static const COUNT:int = 10000;
    
    private var _signal: SignalItem;
    private var _dispatcher: DispatcherItem;
    
    private var t:int;
    private var i:int;
      
    private var test:int;
      
    public function Main() {
      _signal = new SignalItem();
      _signal.signal.add(onSignal);
      
      test = 0;
      t = getTimer();
      
      for (i = 0; i<COUNT; i++) {
        _signal.dispatch();
      }
      
      t = getTimer()-t;
      
      trace("Main.Main(); signal : " + t);
      
      _dispatcher = new DispatcherItem();
      _dispatcher.addEventListener(Event.CHANGE, onChange);
      
      test = 0;
      t = getTimer();
      
      for (i = 0; i<COUNT; i++) {
        _dispatcher.dispatch();
      }
      t = getTimer() - t;
      trace("Main.Main(); dispatcher : " + t);
    }
      private function onSignal(z:int): void {
      test++;
    }
      private function onChange(e:Event): void {
      test++;
    }
  }

  public class SignalItem {
    
    private var _signal:DeluxeSignal;
    
    public function SignalItem() {
      _signal = new DeluxeSignal(this);
    }
    
    public function get signal(): Signal {
      return _signal;
    }
    
    public function dispatch():void {
      _signal.dispatch(new GenericEvent());
    }
  }

  public class DispatcherItem extends EventDispatcher{
    
    public function DispatcherItem() {
      
    }
    
    public function dispatch ():void {
      dispatchEvent(new Event(Event.CHANGE));
    }
    
  }
  


Выводы:

По сути Сигналы являются тем-же патерном Наблюдатель (Observer), то есть альтернативой IEventDispatcher. Производительность ниже, так как библиотека не нативная, но на приемлемом уровне. Использование сигналов может открыть миграцию других, завязанных на них решений, из C# на ActionScript.

Плюс:
— более компактный код

Минус:
-производительность
Tags:
Hubs:
Total votes 30: ↑25 and ↓5+20
Comments18

Articles