框架学习笔记:深度解析StrangeIoC内部运行机制
2015-12-07 18:41
489 查看
StrangeIoC的设计和RobotLegs一致,所以我的解析会对照RobotLegs来看。
整个框架使用的是MVCS的模式,关于MVCS模式大家可以点这里进行查看,这里就不谈了,既然StrangeIoC称为依赖注入框架,我们就直接谈这个框架的注入实现。
大家一定很好奇,当我们挂载了View脚本的GameObject添加到场景时,对应的中介类就会生成并绑定到该GameObject之上,同时中介类会通过注入获取到View的实例,而当GameObject被销毁时中介类也会被移除,下面我们一起看看这里面的玄机吧。
(注意:这里的方法并不是默认的void Start之类的写法,而是作为保护的虚函数进行定义的,这样可以使我们的子类进行对应的扩展。)
下面我们看看这段代码的意思是啥:
两个方法一致,这里判断的意思是:如果还没有在Context中进行注册,则先通过冒泡的方式找到Context(所谓的冒泡就是递归寻找父级对象,如果找到包含了ContextView脚本的父级对象,则认为是找到了Context,这也是为啥所有需要注入功能的GameObject都必须作为子孙对象被放在包含了ContextView脚本的GameObject中的原因。),并告诉它添加了一个视图对象。
![](http://images0.cnblogs.com/blog2015/223374/201508/271856179536982.png)
(为了省事,干脆就把所有的GameObject都放在包含ContextView脚本的GameRoot下吧。)
接下来框架会从我们在Context中注册的信息里找到这个视图对应的中介类类型,并创建它添加到GameObject中,代码在MediationBinder的mapView中,如下:
我们可以在19行看到中介类被添加了。
逻辑与Awake和Start一致,不同的是传递的参数不一致,这里也是通过冒泡找到Context并告诉它我移除了一个视图对象,框架会找到这个中介类并调用其OnRemove方法,代码在MediationBinder的unmapView中,如下:
下面我们来看看注入是怎么实现的。
我们还是以中介类为例子来看,中介类被创建后会进行注入,其中最重要的就是注入对应的视图脚本对象。代码在MediationBinder的mapView中,如下:
这3行的解析如下:
记录添加的视图脚本对象,绑定到视图类型上;
对中介类进行注入,当发现有[Inject]的标签的属性时,同时类型为视图类型,则将上一步记录的视图脚本实例赋值给他;
注入完毕,取消记录的视图脚本对象。
那么注入的核心逻辑呢?我们来看看Injector类的Inject方法:
最开始的3行是做异常判断的;
下面有一个判断,如果是原生类型、数字或字符串就不处理了;
接下来会取出要被注入的对象的所有反射信息;
再接下来就是进行注入,解析如下:
如果要对构造函数的参数进行注入则进行构造函数参数的注入;
对属性进行注入;
对标记了[PostConstruct]标签的方法进行调用;
返回目标对象,此时注入已经完成。
答案就是:使用反射获取类的所有信息,而Attribute可以为类、方法、属性等添加标记,这些标记也是可以由反射获得的,那么框架就可以知道什么属性是需要注入的了,只要这个属性被[Inject]标记即可,框架找到这些属性,通过类型判断按Context中注册的规则(比如是否为单例类型等)将对象的实例赋值给该属性。
如果不作为单例注入,则每次通过[Inject]标签获取的对象都是新创建的实例,哪怕是同一个类写了2个[Inject]同一类型的对象,获取的实例也是两个不同的对象(虽然类型相同)。
但是整个框架中其实存在两种dispatcher,一种负责在MVCS之间进行消息传递(注意这里的V指的是中介类),还有一种是负责视图类发送消息给中介类的dispatcher;
两种dispatcher不通用,即一种发送的消息另一种不会收到。
InSequence表示按照注册的前后顺序来调用命令;
InParallel表示平行执行命令,即不关系命令的前后调用顺序。
整个框架使用的是MVCS的模式,关于MVCS模式大家可以点这里进行查看,这里就不谈了,既然StrangeIoC称为依赖注入框架,我们就直接谈这个框架的注入实现。
中介类的生命周期
为啥不先说注入呢?因为自动创建和销毁中介类是我认为这个框架设计得最精彩的地方。大家一定很好奇,当我们挂载了View脚本的GameObject添加到场景时,对应的中介类就会生成并绑定到该GameObject之上,同时中介类会通过注入获取到View的实例,而当GameObject被销毁时中介类也会被移除,下面我们一起看看这里面的玄机吧。
自动实例化中介类
首先所有的视图脚本都必须继承自View类,所以我们要揭开自动实例化的秘密就必须先从这个类入手,我们先看看View类的Awake和Start方法:/// A MonoBehaviour Awake handler. /// The View will attempt to connect to the Context at this moment. protected virtual void Awake () { if (!registeredWithContext) bubbleToContext(this, true, false); } /// A MonoBehaviour Start handler /// If the View is not yet registered with the Context, it will /// attempt to connect again at this moment. protected virtual void Start () { if (!registeredWithContext) bubbleToContext(this, true, true); }
(注意:这里的方法并不是默认的void Start之类的写法,而是作为保护的虚函数进行定义的,这样可以使我们的子类进行对应的扩展。)
下面我们看看这段代码的意思是啥:
两个方法一致,这里判断的意思是:如果还没有在Context中进行注册,则先通过冒泡的方式找到Context(所谓的冒泡就是递归寻找父级对象,如果找到包含了ContextView脚本的父级对象,则认为是找到了Context,这也是为啥所有需要注入功能的GameObject都必须作为子孙对象被放在包含了ContextView脚本的GameObject中的原因。),并告诉它添加了一个视图对象。
![](http://images0.cnblogs.com/blog2015/223374/201508/271856179536982.png)
(为了省事,干脆就把所有的GameObject都放在包含ContextView脚本的GameRoot下吧。)
接下来框架会从我们在Context中注册的信息里找到这个视图对应的中介类类型,并创建它添加到GameObject中,代码在MediationBinder的mapView中,如下:
/// Creates and registers one or more Mediators for a specific View instance. /// Takes a specific View instance and a binding and, if a binding is found for that type, creates and registers a Mediator. virtual protected void mapView(IView view, IMediationBinding binding) { Type viewType = view.GetType(); if (bindings.ContainsKey(viewType)) { object[] values = binding.value as object[]; int aa = values.Length; for (int a = 0; a < aa; a++) { MonoBehaviour mono = view as MonoBehaviour; Type mediatorType = values [a] as Type; if (mediatorType == viewType) { throw new MediationException(viewType + "mapped to itself. The result would be a stack overflow.", MediationExceptionType.MEDIATOR_VIEW_STACK_OVERFLOW); } MonoBehaviour mediator = mono.gameObject.AddComponent(mediatorType) as MonoBehaviour; if (mediator is IMediator) ((IMediator)mediator).PreRegister (); injectionBinder.Bind (viewType).ToValue (view).ToInject(false); injectionBinder.injector.Inject (mediator); injectionBinder.Unbind(viewType); if (mediator is IMediator) ((IMediator)mediator).OnRegister (); } } }
我们可以在19行看到中介类被添加了。
RobotLegs的做法
AS3中一个显示对象添加到舞台时会触发对应的事件,RobotLegs就是通过监听这个事件,在这个事件中创建中介类的。而StrangeIoC使用的是Awake和Start事件,也可以理解为添加到场景。自动销毁中介类
销毁我们倒回来看看View类中的OnDestroy方法:/// A MonoBehaviour OnDestroy handler /// The View will inform the Context that it is about to be /// destroyed. protected virtual void OnDestroy () { bubbleToContext(this, false, false); }
逻辑与Awake和Start一致,不同的是传递的参数不一致,这里也是通过冒泡找到Context并告诉它我移除了一个视图对象,框架会找到这个中介类并调用其OnRemove方法,代码在MediationBinder的unmapView中,如下:
/// Removes a mediator when its view is destroyed virtual protected void unmapView(IView view, IMediationBinding binding) { Type viewType = view.GetType(); if (bindings.ContainsKey(viewType)) { object[] values = binding.value as object[]; int aa = values.Length; for (int a = 0; a < aa; a++) { Type mediatorType = values[a] as Type; MonoBehaviour mono = view as MonoBehaviour; IMediator mediator = mono.GetComponent(mediatorType) as IMediator; if (mediator != null) { mediator.OnRemove(); } } } }
下面我们来看看注入是怎么实现的。
RobotLegs的做法
同样AS3中一个显示对象从舞台移除时也会触发对应的事件,通过监听这个事件就可以移除对应的中介类了。注入是如何实现的?
刚开始使用这个框架的童鞋肯定会惊叹于为什么写了[Inject]以后就可以自动获取对应的对象的实例,这种技术称为注入,其实原理很简单,就是使用了Attribute和反射两种特性而已。我们还是以中介类为例子来看,中介类被创建后会进行注入,其中最重要的就是注入对应的视图脚本对象。代码在MediationBinder的mapView中,如下:
injectionBinder.Bind (viewType).ToValue (view).ToInject(false); injectionBinder.injector.Inject (mediator); injectionBinder.Unbind(viewType);
这3行的解析如下:
记录添加的视图脚本对象,绑定到视图类型上;
对中介类进行注入,当发现有[Inject]的标签的属性时,同时类型为视图类型,则将上一步记录的视图脚本实例赋值给他;
注入完毕,取消记录的视图脚本对象。
那么注入的核心逻辑呢?我们来看看Injector类的Inject方法:
public object Inject(object target, bool attemptConstructorInjection) { failIf(binder == null, "Attempt to inject into Injector without a Binder", InjectionExceptionType.NO_BINDER); failIf(reflector == null, "Attempt to inject without a reflector", InjectionExceptionType.NO_REFLECTOR); failIf(target == null, "Attempt to inject into null instance", InjectionExceptionType.NULL_TARGET); //Some things can't be injected into. Bail out. Type t = target.GetType (); if (t.IsPrimitive || t == typeof(Decimal) || t == typeof(string)) { return target; } IReflectedClass reflection = reflector.Get (t); if (attemptConstructorInjection) { target = performConstructorInjection(target, reflection); } performSetterInjection(target, reflection); postInject(target, reflection); return target; }
最开始的3行是做异常判断的;
下面有一个判断,如果是原生类型、数字或字符串就不处理了;
接下来会取出要被注入的对象的所有反射信息;
再接下来就是进行注入,解析如下:
如果要对构造函数的参数进行注入则进行构造函数参数的注入;
对属性进行注入;
对标记了[PostConstruct]标签的方法进行调用;
返回目标对象,此时注入已经完成。
注入实现解说
好吧,还是没搞懂注入究竟是怎么实现的?下面就大概说一下思路,不看代码了:答案就是:使用反射获取类的所有信息,而Attribute可以为类、方法、属性等添加标记,这些标记也是可以由反射获得的,那么框架就可以知道什么属性是需要注入的了,只要这个属性被[Inject]标记即可,框架找到这些属性,通过类型判断按Context中注册的规则(比如是否为单例类型等)将对象的实例赋值给该属性。
一些其他的知识点
ToSingleton
作为单例注入,其作用是保证每次通过[Inject]标签获取的对象都是同一个对象,即只有一个实例,该实例在第一次获取时进行创建。如果不作为单例注入,则每次通过[Inject]标签获取的对象都是新创建的实例,哪怕是同一个类写了2个[Inject]同一类型的对象,获取的实例也是两个不同的对象(虽然类型相同)。
dispatcher和IEvent
整个框架内部进行消息传递都靠这个类实现;但是整个框架中其实存在两种dispatcher,一种负责在MVCS之间进行消息传递(注意这里的V指的是中介类),还有一种是负责视图类发送消息给中介类的dispatcher;
两种dispatcher不通用,即一种发送的消息另一种不会收到。
Once和InSequence
Once表示立即执行命令,并且命令执行完毕后就移除该命令的映射关系;InSequence表示按照注册的前后顺序来调用命令;
InParallel表示平行执行命令,即不关系命令的前后调用顺序。
相关文章推荐
- java使double保留两位小数的多方法 java保留两位小数
- Truncate、Drop与Delete区别
- 深入理解c++中char*与wchar_t*与string以及wstring之间的相互转换
- hdu5592 ZYB's Premutation
- 框架学习笔记:Unity3D的MVC框架——StrangeIoC
- 【2015广东工业大学新生赛I】【贪心】Stubird说这是水题 给定正视图和侧视图求最少积木数
- 【2015广东工业大学新生赛H】【数论 先设再求思想】跑操场 不同速度第n次相遇的时间
- fedora23 搜狗安装过程
- APPd的职位分工
- SNMP中MIB2所有主要节点
- 关于HTML、XHTML、CSS、XML的区别
- 携程2016校园招聘笔试题分析
- 【2015广东工业大学新生赛G】【区间统计 左右端点计数思想】我是好人2 区间范围有多少个数 模x余y
- 程序输入表修复
- javascript 输入框显示默认的提示语句
- 路由选择过程与原理
- 【数据结构】图结构操作示例
- snmp信息的查询命令snmpwalk
- Debug文件夹是什么
- 【2015广东工业大学新生赛F】【脑洞 DP打表找规律】有钱的wingkou 取走2个放回1个问最后剩下为0的概率