天天看點

AS3 Signals之入門篇

 這篇文章詳細的介紹了Robert Penner的AS3 Signals是什麼,以及如何使用它讓對象間的溝通更迅捷。它可以避免你使用正常的ActionScript 事件機制,用到的 代碼量更少。

我們将通過範例來了解不同類型的signals,進而向大家描繪出如何在實際項目中 應用signals。我希望你會和我一樣,喜歡上AS3 Signals帶來的對象溝通友善的感覺。其中一個裨益就是Signals非常容易掌握應用。你将很快實作它,它也會帶來更多的便捷。那麼我們開始吧!

如果你想親曆親為,也是不錯的主意,可以直接去到GitHub并下載下傳AS3 Signals SWC 檔案,在你中意的IDE裡建立項目(比如我會選擇FDT)并導入SWC檔案到你的項目中去。

什麼是AS3 Signals呢?

Signals是一個AS3事件的新的機制,靈感來自于C#的事件和Qt中的signals / slots。(Robert Penner)

Robert Penner宣傳Signals是“脫離事件獨自思考”。這個口号用來描述Signals是一種輕量級并且是強制類型的AS3 通信工具。

這已經說明了一些内幕,更詳細的解釋是:

Signals 是某種程度上可以替代AS3内置事件的 架構。

它在 API裡整合了結合C#優異的signals思想和功能性的事件思想為一體。它比内置的事件更為迅捷,也少了很多呆闆的代碼。更難能可貴的是,它裡面同樣可以使用内置的事件,而且是内置事件運作速度的4倍。

什麼是Signal?

一個Signal其本質上是一個微型的針對某個事件的特定的派發者,附帶它本身的監聽者數組。

一個Signal的基本概念就是,不會使用類似内置事件那種方式、基于字元串的頻道,而是化為一個類中具體的event/signal成員。這意味着我們這些 開發者,在對象之間如何連接配接和溝通等方面,有了更多的控制。我們不再需要字元串變量來代表我們的Signals。Signals能夠表達為真正的對象。

如果你已經是一個ActionScript開發者,有時候在你的項目裡建立自定義的事件子類時就會相當痛苦。定義常量,定義事件等等。添加監聽者,移除監聽者,并總是在你的派發類裡繼承 EventDispatcher 。你将會發現Signals會在很短時間使這一切變得輕松自如。

Signal的基礎

從最基本的Signal開始吧,比如我們用它來替代一個自定義AS3事件。Signal可以簡單的視為一個對象。一個基本的Signals屬于你的派發類的一個public屬性。

首先能發現的好處就是我們不再像使用内置的事件體系一樣必須擴充EventDispatcher類來實作派發類。

來看看一個簡單的AlarmClock例子來了解文法。

[inside ria.basicSignal.AlarmClock.as]

  1. package insideria.basicSignal
  2. {
  3.         import org.osflash.signals.Signal;
  4.         public class AlarmClock
  5.         {
  6.                   public var alarm:Signal;
  7.                   public function AlarmClock()
  8.                   {
  9.                            alarm = new Signal();
  10.                    }
  11.                   public function ring():void
  12.                   {
  13.                            alarm.dispatch();
  14.                    }
  15.           }
  16. }

使用AlarmClock,我們首先把它執行個體化。之後我們使用alarm這個public屬性的add() 方法,也就是我們的Signal,來添加一個名為onRing的監聽者。然後我們需要鬧鐘響起了,是以我們調用它的ring() 方法。就是這樣輕松。如果你釋出了程式,你将在控制台看到“Wake up !”的出現。

傳遞參數

基本的Signal的本領不止如此。我相信你迫切想知道我們如何使用Signal來傳遞參數。這也很簡單,來讓我們更新AlarmClock類,進而可以傳遞time給我們的WakeUp類。

[insideria.basicSignalArguments.AlarmClock.as]

  1. package insideria.basicSignalArguments
  2. {
  3.         import org.osflash.signals.Signal;
  4.         public class AlarmClock
  5.         {
  6.                   public var alarm:Signal;
  7.                   public function AlarmClock()
  8.                   {
  9.                            alarm = new Signal(String);
  10.                    }
  11.                   public function ring():void
  12.                   {
  13.                            alarm.dispatch("9 AM");
  14.                    }
  15.           }
  16. }

複制代碼 So all we have to do to deliver a strongly-typed argument (you can also deliver untyped arguments) via our Signal is defining the type in the Signals constructor and dispatching whatever we want in the dispatch() method. You can pass multiple arguments of same or different type by separating them with a comma.

因為我們所作的改動就是通過我們的Signal傳遞了一個強制類型的參數(你也可以傳遞無類别的參數),它會在Signal的構造函數裡聲明,使用dispatch()來派發任何想要傳遞的參數。你也傳遞多個同類型或不同類型的參數,将它們用","分割開就行。

  1. alarm = new Signal(String, Number, int, uint, Boolean);
  2. alarm.dispatch("9 AM", 45.4, -10, 5, true);

複制代碼 現在來看看我們的主程式接口如何trace出time吧。

[insideria.basicSignalArguments.WakeUp.as]

  1. package insideria.basicSignalArguments
  2. {
  3.         import flash.display.Sprite;
  4.         public class WakeUp extends Sprite
  5.         {
  6.                   private var alarmClock:AlarmClock;
  7.                   public function WakeUp()
  8.                   {
  9.                            alarmClock = new AlarmClock();
  10.                            alarmClock.alarm.add(onRing);
  11.                            alarmClock.ring();
  12.                    }
  13.                   private function onRing(time:String):void
  14.                   {
  15.                            trace("Wake up! It's already " + time);
  16.                    }
  17.           }
  18. }

複制代碼 Signal的監聽者現在必須添加一個參數。否則編譯器會抛出參數錯誤。為了清晰起見我們還稱它為time。注意這個參數也要聲明類别。現在我們運作程式,控制台會說“Wake up ! It's already 9 AM”.

添加和移除Signals

在實際程式設計中你經常遇到一種情形:有個事件你隻需要激活一次,然後要立即銷毀。比起内置事件,Signals提供了一個輕松的方式來完成,而不需要寫那一堆煩人的代碼,比如like addEventListener(...), removeEventListener(...).

是以我們來修改下WakeUp,替換add()方法為addOnce(),讓它隻捕捉Signal一次。

  1. alarmClock.alarm.addOnce(onRing);
複制代碼

  這樣監聽者将隻會被執行一次,然後它會被自動的移除。如果使用的是add()方法,我們需要移除監聽者,即可以使用remove(listener)方法,也可以使用Singnal的removeAll()方法,如果特定的Singnal附加多個監聽對象的話。來看看它是什麼樣的:

alarmClock.alarm.remove(onRing);

alarmClock.alarm.removeAll();

目前為止我們已經了解了很多經常使用到的特性。我猜你也會好奇如何從Signal中擷取更多的資訊。比如在内置事件裡,會告訴我們目标來源的一切。Signal也能做到,繼續看下去。

DeluxeSignal和GenericEvent

DeluxeSignal要比我們慣用的内置ActionScript事件更近一步。DeluxeSignal能讓我們通路目标,尤其重要的是,可以通路到Signal本身。是以我們可以操作二者。當然DeluxeSignal也擁有基本Signal的功能。現在來更新我們的AlarmClock,結合GenericEvent來使用DeluxeSignal。從這個過程中你會了解GenericEvent的。

[insideria.deluxeSignal.AlarmClock.as]

  1. package insideria.deluxeSignal
  2. {
  3.         import org.osflash.signals.DeluxeSignal;
  4.         import org.osflash.signals.events.GenericEvent;
  5.         public class AlarmClock
  6.         {
  7.                   public var alarm:DeluxeSignal;
  8.                   public var message:String;
  9.                   public function AlarmClock()
  10.                   {
  11.                            alarm = new DeluxeSignal(this);
  12.                            message = "This is a message from our AlarmClock";
  13.                    }
  14.                   public function ring():void
  15.                   {
  16.                            alarm.dispatch(new GenericEvent());
  17.                    }
  18.           }
  19. }

複制代碼 我們建立了一個DeluxeSignal的公開屬性,并在構造函數裡執行個體化,傳遞了this到DeluxeSignal的構造函數裡。這樣DeluxeSignal會知道AlarmClock。我們還建立了一個message的公開屬性,可以在之後被監聽者通路到。

這是一個示範如何用GenericEvent來傳遞多個參數的例子。說到GenericEvent我們要看看ring()方法。DeluxeSignal的派發和之前我們使用的基本Signal一樣,不過這次我們傳遞了一個GenericEvent的執行個體給它。這個GenericEvent将附帶Signal的來源以及關于它本身的資訊。讓我們看看如何在主程式檔案裡通路這些資訊吧。

[insideria.deluxeSignal.WakeUp.as]

  1. package insideria.deluxeSignal
  2. {
  3.         import org.osflash.signals.events.GenericEvent;
  4.         import flash.display.Sprite;
  5.         public class WakeUp extends Sprite
  6.         {
  7.                   private var alarmClock:AlarmClock;
  8.                   public function WakeUp()
  9.                   {
  10.                            alarmClock = new AlarmClock();
  11.                            alarmClock.alarm.add(onRing);
  12.                            alarmClock.ring();
  13.                    }
  14.                   private function onRing(event:GenericEvent):void
  15.                   {
  16.                            trace(event.target);
  17.                            trace(event.signal);
  18.                            trace(event.target.message);
  19.                    }
  20.           }
  21. }

複制代碼 隻有監聽者改動過了。監聽者現在接收GenericEvent的參數,就是我們通過AlarmClock類來傳遞的那個。

要記住我們就是從這裡擷取資訊的。現在我們可以trace下, 如同我們在内置事件裡做的那樣,可以擷取到target,以及像是message等的其他屬性。唯一特别的是我們也可以通路到AlarmClock類裡派發事件的Signal對象     我們已經學習了兩種Signal。接下來了解下如何通過AS3 Signal來使用内置事件吧。

NativeSignals

最終AS3 Signals能作為一個完整的内置事件替代品是它可以讓開發者通路到内置事件。Robert Penner對這項任務也非常小心在意的。

因為我總是喜歡給出一些實際的範例,因為我們先來看看NativeSignal是如何在運作時加載另一個SWF檔案。

那我們第一件要做的事就是建立一個我們用來載入的SWF吧。

因為我們需要在下面的類中用到ADDED_TO_STAGE事件,首先關注下NativeSignal類的使用。

[insideria.nativeSignals.AddToStage.as]

  1. package insideria.nativeSignals
  2. {
  3.         import org.osflash.signals.natives.NativeSignal;
  4.         import flash.display.Sprite;
  5.         import flash.events.Event;
  6.         public class AddToStage extends Sprite
  7.         {
  8.                   public function AddToStage()
  9.                   {
  10.                            var added:NativeSignal = new NativeSignal(this, Event.ADDED_TO_STAGE, Event);
  11.                            added.addOnce(onAdded);
  12.                    }
  13.                   private function onAdded(event:Event):void
  14.                   {
  15.                            graphics.beginFill(0xCCCCCC);
  16.                            graphics.drawRect(0, 0, 100, 100);
  17.                            graphics.endFill();
  18.                    }
  19.           }
  20. }

複制代碼 如你所見,我們建立了一個本地的NativeSignal,第一個傳參為目标this,第二個參數是我們要使用的事件,第三個參數也是一個事件類。和我們之前讨論過的基礎Signals一樣,我們使用addOnce方法,僅僅執行Signals一次。當Signals被執行過一次後,我們不需要擔心如何清除它。

sible to perhaps click on.

現在,就像我們真的在使用内置事件一樣,我們建立了一個onAdded的回調函數。執行函數接收了一個類型為Event的事件參數。確定在執行函數裡傳遞的參數和在NativeSignal執行個體中的事件類别一緻。

在我們的onAdded回調函數裡,我們隻是繪制了一些随機圖像來確定它确實被加載到其他檔案中了。當然我們可以用NativeSignal來做更多的東西,比如有一些可視的物件能點選。

現在開始建立我們要用到的這個個SWF檔案吧。   建立loading class

我們已經建立了一個可視化的,擴充自Sprite的類。下面我們需要3個private屬性來含有我們的native Signals。别被這些private的屬性迷惑住了————他們不會像基本的Signal那樣需要在外部通路到。

private var loadedSignal:NativeSignal;

private var progressSignal:NativeSignal;

private var faultSignal:NativeSignal;

下面我們需要加載二進制檔案的基礎設定。是以我們建立了一個URLRequest,一個Loader,一個指向Signals的EventDispatcher。

var request:URLRequest = new URLRequest("AddToStage.swf");

var loader

AS3 Signals之入門篇

oader = new Loader();

var signalTarget:IEventDispatcher = loader.contentLoaderInfo;

現在我們将使用NativeSignal類來建立一個本地的Signal,服務于我們的加載事件,progress事件和預設事件。我們将儲存我們的loader執行個體的contentLoaderInfo屬性為一個IEventDispatcher的變量。

loadedSignal = new NativeSignal(signalTarget, Event.COMPLETE, Event);

loadedSignal.addOnce(onLoaded);

progressSignal = new NativeSignal(signalTarget, ProgressEvent.PROGRESS, ProgressEvent);

progressSignal.add(onProgress);

faultSignal = new NativeSignal(signalTarget, IOErrorEvent.IO_ERROR, IOErrorEvent);

faultSignal.addOnce(onFault);

建立這樣的一個本地Signal對象的文法有如下幾點要注意:第一個參數是目标,第二個是附帶常量的事件,第三個仍舊是event類。另外一點是保持event handler裡的參數類别和在native Signal對象裡用到的一緻。最後一點就是要注意确實已經開始加載行為了。

loader.load(request);

接下來我們需要設定處理Signals的方法。

  1. private function onFault(event:IOErrorEvent):void
  2. {
  3.         trace("Something went wrong!");
  4. }
  5. private function onProgress(event:ProgressEvent):void
  6. {
  7.         trace((event.bytesLoaded / event.bytesTotal) * 100);
  8. }
  9. private function onLoaded(event:Event):void
  10. {
  11.         progressSignal.removeAll();
  12.         addChild(event.target.content);
  13. }

複制代碼    

這樣我們可以在加載出錯時作出響應,我們可以在加載過程中顯示進度,而且在完成後把内容添加進display list,也會把progress Signal移除,因為我們不再使用它了。如果你已經了解了使用add()和addOnce()的概念,很明顯我們應該在fault和complete裡使用addOnce,因為它們隻需要執行一次。而add()應該用在progress裡,因為它在加載完成之前,會執行很多次。

如果你運作程式,你會看到在左上角有一個100x100像素的方塊。如果有哪裡出了問題,控制台會提示“Something went wrong!”,因為我們已經寫死到類裡了。如果你遇到錯誤,請確定URLRequest執行個體裡的連結路徑确實指向了我們希望加載的SWF檔案路徑。

下面是已經完成的完整代碼:

[insideria.nativeSignals.Loading.as]

  1. package insideria.nativeSignals
  2. {
  3.         import org.osflash.signals.natives.NativeSignal;
  4.         import flash.display.Loader;
  5.         import flash.display.Sprite;
  6.         import flash.events.Event;
  7.         import flash.events.IEventDispatcher;
  8.         import flash.events.IOErrorEvent;
  9.         import flash.events.ProgressEvent;
  10.         import flash.net.URLRequest;
  11.         public class Loading extends Sprite
  12.         {
  13.                   private var loadedSignal:NativeSignal;
  14.                   private var progressSignal:NativeSignal;
  15.                   private var faultSignal:NativeSignal;
  16.                   public function Loading()
  17.                   {
  18.                            var request:URLRequest = new URLRequest("AddToStage.swf");
  19.                            var loader:Loader = new Loader();
  20.                            var signalTarget:IEventDispatcher = loader.contentLoaderInfo;
  21.                            loadedSignal = new NativeSignal(signalTarget, Event.COMPLETE, Event);
  22.                            loadedSignal.addOnce(onLoaded);
  23.                            progressSignal = new NativeSignal(signalTarget, ProgressEvent.PROGRESS, ProgressEvent);
  24.                            progressSignal.add(onProgress);
  25.                            faultSignal = new NativeSignal(signalTarget, IOErrorEvent.IO_ERROR, IOErrorEvent);
  26.                            faultSignal.addOnce(onFault);
  27.                            loader.load(request);
  28.                    }
  29.                   private function onFault(event:IOErrorEvent):void
  30.                   {
  31.                            trace("Something went wrong!");
  32.                    }
  33.                   private function onProgress(event:ProgressEvent):void
  34.                   {
  35.                            trace((event.bytesLoaded / event.bytesTotal) * 100);
  36.                    }
  37.                   private function onLoaded(event:Event):void
  38.                   {
  39.                            progressSignal.removeAll();
  40.                            addChild(event.target.content);
  41.                    }
  42.           }
  43. }
複制代碼

    處理MouseEvents

我會給出另外一個例子來說明,比如如何處理點選事件。傳回AddToStage類,我們添加2個private屬性。

private var clicked:NativeSignal;

private var box:Sprite;

現在在box裡繪制一個随機圖形,并把它添加到display list。

box = new Sprite();

box.graphics.beginFill(0xCCCCCC);

box.graphics.drawRect(0, 0, 100, 100);

box.graphics.endFill();

addChild(box);

最後添加native clicksignal和回調函數

clicked = new NativeSignal(box, MouseEvent.CLICK, MouseEvent);

clicked.add(onClicked);

private function onClicked(event:MouseEvent):void

{

        trace("clicked");

}

釋出一下這個檔案,編譯成SWF,然後運作那個loading SWF.當你點選box,你的控制台會在每次點選時顯示“clicked”

現在我們不但通過AS3 Signals來加載外部檔案,還注冊了一個點選監聽,并學習了如何使用ActionScript的内置事件。這種方式是廣泛适用的。

結語

這篇文章你可以學到使用Robert Penner的AS3 Signal是如何容易。我們分别了解了3種Signals,以及如何使用它們。我希望這篇文章對你有所裨益。也希望你能喜歡這種交流方式。最後我也要分享一些資源給大家。

    * AS3 Signals on GitHub

    * Community examples

    * Templates for FDT

最後, 這裡是源檔案: Signals.zip

附記:AS3 Signals也支援Robotlegs!