框架學習筆記:深度解析StrangeIoC內部運行機制


StrangeIoC的設計和RobotLegs一致,所以我的解析會對照RobotLegs來看。

整個框架使用的是MVCS的模式,關於MVCS模式大家可以點這里進行查看,這里就不談了,既然StrangeIoC稱為依賴注入框架,我們就直接談這個框架的注入實現。

中介類的生命周期

為啥不先說注入呢?因為自動創建和銷毀中介類是我認為這個框架設計得最精彩的地方。

大家一定很好奇,當我們掛載了View腳本的GameObject添加到場景時,對應的中介類就會生成並綁定到該GameObject之上,同時中介類會通過注入獲取到View的實例,而當GameObject被銷毀時中介類也會被移除,下面我們一起看看這里面的玄機吧。

自動實例化中介類

首先所有的視圖腳本都必須繼承自View類,所以我們要揭開自動實例化的秘密就必須先從這個類入手,我們先看看View類的Awake和Start方法:

 1 /// A MonoBehaviour Awake handler.
 2 /// The View will attempt to connect to the Context at this moment.
 3 protected virtual void Awake ()
 4 {
 5     if (!registeredWithContext)
 6         bubbleToContext(this, true, false);
 7 }
 8 
 9 /// A MonoBehaviour Start handler
10 /// If the View is not yet registered with the Context, it will 
11 /// attempt to connect again at this moment.
12 protected virtual void Start ()
13 {
14     if (!registeredWithContext)
15         bubbleToContext(this, true, true);
16 }

注意:這里的方法並不是默認的void Start之類的寫法,而是作為保護的虛函數進行定義的,這樣可以使我們的子類進行對應的擴展。

下面我們看看這段代碼的意思是啥:

兩個方法一致,這里判斷的意思是:如果還沒有在Context中進行注冊,則先通過冒泡的方式找到Context(所謂的冒泡就是遞歸尋找父級對象,如果找到包含了ContextView腳本的父級對象,則認為是找到了Context,這也是為啥所有需要注入功能的GameObject都必須作為子孫對象被放在包含了ContextView腳本的GameObject中的原因。),並告訴它添加了一個視圖對象。

(為了省事,干脆就把所有的GameObject都放在包含ContextView腳本的GameRoot下吧。)

接下來框架會從我們在Context中注冊的信息里找到這個視圖對應的中介類類型,並創建它添加到GameObject中,代碼在MediationBinder的mapView中,如下:

 1 /// Creates and registers one or more Mediators for a specific View instance.
 2 /// Takes a specific View instance and a binding and, if a binding is found for that type, creates and registers a Mediator.
 3 virtual protected void mapView(IView view, IMediationBinding binding)
 4 {
 5     Type viewType = view.GetType();
 6 
 7     if (bindings.ContainsKey(viewType))
 8     {
 9         object[] values = binding.value as object[];
10         int aa = values.Length;
11         for (int a = 0; a < aa; a++)
12         {
13             MonoBehaviour mono = view as MonoBehaviour;
14             Type mediatorType = values [a] as Type;
15             if (mediatorType == viewType)
16             {
17                 throw new MediationException(viewType + "mapped to itself. The result would be a stack overflow.", MediationExceptionType.MEDIATOR_VIEW_STACK_OVERFLOW);
18             }
19             MonoBehaviour mediator = mono.gameObject.AddComponent(mediatorType) as MonoBehaviour;
20             if (mediator is IMediator)
21                 ((IMediator)mediator).PreRegister ();
22             injectionBinder.Bind (viewType).ToValue (view).ToInject(false);
23             injectionBinder.injector.Inject (mediator);
24             injectionBinder.Unbind(viewType);
25             if (mediator is IMediator)
26                 ((IMediator)mediator).OnRegister ();
27         }
28     }
29 }

我們可以在19行看到中介類被添加了。

RobotLegs的做法

AS3中一個顯示對象添加到舞台時會觸發對應的事件,RobotLegs就是通過監聽這個事件,在這個事件中創建中介類的。而StrangeIoC使用的是Awake和Start事件,也可以理解為添加到場景。

自動銷毀中介類

銷毀我們倒回來看看View類中的OnDestroy方法:

1 /// A MonoBehaviour OnDestroy handler
2 /// The View will inform the Context that it is about to be
3 /// destroyed.
4 protected virtual void OnDestroy ()
5 {
6     bubbleToContext(this, false, false);
7 }

邏輯與Awake和Start一致,不同的是傳遞的參數不一致,這里也是通過冒泡找到Context並告訴它我移除了一個視圖對象,框架會找到這個中介類並調用其OnRemove方法,代碼在MediationBinder的unmapView中,如下:

 1 /// Removes a mediator when its view is destroyed
 2 virtual protected void unmapView(IView view, IMediationBinding binding)
 3 {
 4     Type viewType = view.GetType();
 5 
 6     if (bindings.ContainsKey(viewType))
 7     {
 8         object[] values = binding.value as object[];
 9         int aa = values.Length;
10         for (int a = 0; a < aa; a++)
11         {
12             Type mediatorType = values[a] as Type;
13             MonoBehaviour mono = view as MonoBehaviour;
14             IMediator mediator = mono.GetComponent(mediatorType) as IMediator;
15             if (mediator != null)
16             {
17                 mediator.OnRemove();
18             }
19         }
20     }
21 }

下面我們來看看注入是怎么實現的。

RobotLegs的做法

同樣AS3中一個顯示對象從舞台移除時也會觸發對應的事件,通過監聽這個事件就可以移除對應的中介類了。

注入是如何實現的?

剛開始使用這個框架的童鞋肯定會驚嘆於為什么寫了[Inject]以后就可以自動獲取對應的對象的實例,這種技術稱為注入,其實原理很簡單,就是使用了Attribute和反射兩種特性而已。

我們還是以中介類為例子來看,中介類被創建后會進行注入,其中最重要的就是注入對應的視圖腳本對象。代碼在MediationBinder的mapView中,如下:

1 injectionBinder.Bind (viewType).ToValue (view).ToInject(false);
2 injectionBinder.injector.Inject (mediator);
3 injectionBinder.Unbind(viewType);

這3行的解析如下:

  1. 記錄添加的視圖腳本對象,綁定到視圖類型上;
  2. 對中介類進行注入,當發現有[Inject]的標簽的屬性時,同時類型為視圖類型,則將上一步記錄的視圖腳本實例賦值給他;
  3. 注入完畢,取消記錄的視圖腳本對象。

那么注入的核心邏輯呢?我們來看看Injector類的Inject方法:

 1 public object Inject(object target, bool attemptConstructorInjection)
 2 {
 3     failIf(binder == null, "Attempt to inject into Injector without a Binder", InjectionExceptionType.NO_BINDER);
 4     failIf(reflector == null, "Attempt to inject without a reflector", InjectionExceptionType.NO_REFLECTOR);
 5     failIf(target == null, "Attempt to inject into null instance", InjectionExceptionType.NULL_TARGET);
 6 
 7     //Some things can't be injected into. Bail out.
 8     Type t = target.GetType ();
 9     if (t.IsPrimitive || t == typeof(Decimal) || t == typeof(string))
10     {
11         return target;
12     }
13 
14     IReflectedClass reflection = reflector.Get (t);
15 
16     if (attemptConstructorInjection)
17     {
18         target = performConstructorInjection(target, reflection);
19     }
20     performSetterInjection(target, reflection);
21     postInject(target, reflection);
22     return target;
23 }

最開始的3行是做異常判斷的;

下面有一個判斷,如果是原生類型、數字或字符串就不處理了;

接下來會取出要被注入的對象的所有反射信息;

再接下來就是進行注入,解析如下:

  1. 如果要對構造函數的參數進行注入則進行構造函數參數的注入;
  2. 對屬性進行注入;
  3. 對標記了[PostConstruct]標簽的方法進行調用;
  4. 返回目標對象,此時注入已經完成。

注入實現解說

好吧,還是沒搞懂注入究竟是怎么實現的?下面就大概說一下思路,不看代碼了:

答案就是:使用反射獲取類的所有信息,而Attribute可以為類、方法、屬性等添加標記,這些標記也是可以由反射獲得的,那么框架就可以知道什么屬性是需要注入的了,只要這個屬性被[Inject]標記即可,框架找到這些屬性,通過類型判斷按Context中注冊的規則(比如是否為單例類型等)將對象的實例賦值給該屬性。

一些其他的知識點

ToSingleton

作為單例注入,其作用是保證每次通過[Inject]標簽獲取的對象都是同一個對象,即只有一個實例,該實例在第一次獲取時進行創建。

如果不作為單例注入,則每次通過[Inject]標簽獲取的對象都是新創建的實例,哪怕是同一個類寫了2個[Inject]同一類型的對象,獲取的實例也是兩個不同的對象(雖然類型相同)。

dispatcher和IEvent

整個框架內部進行消息傳遞都靠這個類實現;

但是整個框架中其實存在兩種dispatcher,一種負責在MVCS之間進行消息傳遞(注意這里的V指的是中介類),還有一種是負責視圖類發送消息給中介類的dispatcher;

兩種dispatcher不通用,即一種發送的消息另一種不會收到。

Once和InSequence

Once表示立即執行命令,並且命令執行完畢后就移除該命令的映射關系;

InSequence表示按照注冊的前后順序來調用命令;

InParallel表示平行執行命令,即不關系命令的前后調用順序。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM