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行的解析如下:
- 記錄添加的視圖腳本對象,綁定到視圖類型上;
- 對中介類進行注入,當發現有[Inject]的標簽的屬性時,同時類型為視圖類型,則將上一步記錄的視圖腳本實例賦值給他;
- 注入完畢,取消記錄的視圖腳本對象。
那么注入的核心邏輯呢?我們來看看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行是做異常判斷的;
下面有一個判斷,如果是原生類型、數字或字符串就不處理了;
接下來會取出要被注入的對象的所有反射信息;
再接下來就是進行注入,解析如下:
- 如果要對構造函數的參數進行注入則進行構造函數參數的注入;
- 對屬性進行注入;
- 對標記了[PostConstruct]標簽的方法進行調用;
- 返回目標對象,此時注入已經完成。
注入實現解說
好吧,還是沒搞懂注入究竟是怎么實現的?下面就大概說一下思路,不看代碼了:
答案就是:使用反射獲取類的所有信息,而Attribute可以為類、方法、屬性等添加標記,這些標記也是可以由反射獲得的,那么框架就可以知道什么屬性是需要注入的了,只要這個屬性被[Inject]標記即可,框架找到這些屬性,通過類型判斷按Context中注冊的規則(比如是否為單例類型等)將對象的實例賦值給該屬性。
一些其他的知識點
ToSingleton
作為單例注入,其作用是保證每次通過[Inject]標簽獲取的對象都是同一個對象,即只有一個實例,該實例在第一次獲取時進行創建。
如果不作為單例注入,則每次通過[Inject]標簽獲取的對象都是新創建的實例,哪怕是同一個類寫了2個[Inject]同一類型的對象,獲取的實例也是兩個不同的對象(雖然類型相同)。
dispatcher和IEvent
整個框架內部進行消息傳遞都靠這個類實現;
但是整個框架中其實存在兩種dispatcher,一種負責在MVCS之間進行消息傳遞(注意這里的V指的是中介類),還有一種是負責視圖類發送消息給中介類的dispatcher;
兩種dispatcher不通用,即一種發送的消息另一種不會收到。
Once和InSequence
Once表示立即執行命令,並且命令執行完畢后就移除該命令的映射關系;
InSequence表示按照注冊的前后順序來調用命令;
InParallel表示平行執行命令,即不關系命令的前后調用順序。