Ice框架提供了不少回调设施,其中一些是使用Ice远程调用进行ami模式或amd模式的支撑。本篇来看一下用于代理端的回调设施。
Ice代码中有好几个Callback相关命名的基类,并且slice还会为你描述的接口,每个接口方法生成好几个回调类,让人眼花缭乱。其实认真整理归纳一下,就会发现,为我们提供的代理调用回调接口只有两个,一个是较为底层一点的,你需要解释Future,另一个是较为高级一点的,框架为你解释好Future,并以输出参数回调。而回调的挂接点是在网络层的io读写完成事件。较为底层一点的回调接口有两个函数,分别是sent(const AsyncResultPtr&)和completed(const AsyncResultPtr&),这两个回调函数分别挂接在网络层的io写完成事件以及io读完成事件上。较为高级一点的回调接口,有sent,response和exception三个函数入口,底层的回调处理completed将读出的响应结果分类为exception或正常输出参数,分别递交给exception或response回调入口。它们的关系如下图:
OutgoingAsync是所有具体Future类的直接基类(,继承树最后一层基类),本身就是一个Callback类,挂接回调在网络层。当ConnectionI对网络io事件进行dispatch调度时,就会满足条件下相应回调OutgoingAsync。OutgoingAsync自身有一套回调框架,可以使回调同步在io执行dispatch调度的线程上,或异步执行在其它线程上。不论那种情况,Outgoing最后将回调委托在基类CallbackCompletion。CallbackCompletion再委托给上层提供的回调。通过回调离开Future层进入的是由Slice为我们生成的框架代码,这时使用的回调接口,才是我们上面说的较为底层的回调接口sent(const AsyncResultPtr&)和completed(const AsyncResultPtr&)。Future以参数方式委托给上层代码进行事件回调处理。这个委托我们是可以在创建一个Future进行指定的。我们有两种选择,一是提供较为底层的回调方式的委托,二是提供较为高级方式的版本。Slice为我们生成的5个begin_Method函数中,有两个是指定底层次版本回调,另两个是指定高级版本回调的,最一个是不使用回调的。我们看一下slice生成的begin_Method函数模板:
::Ice::AsyncResultPtr begin_Method(const ::Ice::Context& context = ::Ice::noExplicitContext);
::Ice::AsyncResultPtr begin_Method(const ::Ice::CallbackPtr& del, const ::Ice::LocalObjectPtr& cookie = );
::Ice::AsyncResultPtr begin_Method(const ::Ice::Context& context, const ::Ice::CallbackPtr& del, const ::Ice::LocalObjectPtr& cookie = );
::Ice::AsyncResultPtr begin_Method(const ::Test::Callback_MyClass_opOnewayPtr& del, const ::Ice::LocalObjectPtr& cookie = );
::Ice::AsyncResultPtr begin_Method(const ::Ice::Context& context, const ::Test::Callback_MyClass_opOnewayPtr& del, const ::Ice::LocalObjectPtr& cookie = );
::Ice::AsyncResultPtr _iceI_begin_Method(const ::Ice::Context&, const ::IceInternal::CallbackBasePtr& del, const ::Ice::LocalObjectPtr& cookie = , bool sync = false);
在每个函数的都有一个参数,类型为Callback相关,命名为del,其实就是delegate,我们提供的Callback是回调处理中的委托目标,而这个委托又会委托给我们自定义的Callback中去。
我们要如何使用代理端的远程调用的回调呢,请看下图:
红色的类,就是我们要提供给Ice的回调类,这里要注意的是,我们的自定义的回调类并不继承于任何一个Callback基类。因为我们自定义的回调类是蓝色的框架提供的回调类委托的目标,而蓝色的由框架提供的回调类,却是OutgoingAsync(基类为AsyncResult的Future)的回调委托目标。其实我们提供的回调类,到最后回调到达已经是第二层委托了。我们提供的回调类唯一要继承的就是Shared。蓝色的类就像一个Binder绑定我们的提供的回调类以及我们选择为这个回调的函数。蓝色类继承树上的子类实例是我们调用Slice为我们生成begin_Method需要传入的回调委托,它绑定我们自定义的回调类和我们选用作本次远程调用回调的函数。蓝色类的继承自CallbackBase,CallbackBase也定义了回调虚接口sent和completed,为什么我们自定义的回调不是直接继承这个有回调虚接口呢?原因是那样的话,我们的自定义的回调类就必然会很多,不利于我们维护。尽管Slice为我们生成了许多我们压根都不会使用到的类,但是我们是不用去维护这些类的。使用绑定(binder,绑定target和function selector)以及委托的方式,我们就可以方便开发出处理多种回调函数的回调类。我们的回调委托并不是固定绑定到某一个对象调用的上,而是只对应某一次对象调用,并且我们可以为每次对象调用选用自定义回调类的不同成员函数进行绑定回调处理。
我们的回调起源于网络服务层的io读写完成事件,经过AsyncResult(Future层)的回调框架最后委托到我们回调代码。那么AsyncResult(Future层)的回调框架又为我们做了些什么工作呢?首先总览AsyncResult的类图:
所有具体的Future类都多继承自两个基类CallbackCompletion和OutgoingAsync,可参考最下方同一水平层次的三个类。最顶层的基类有两员分别是,OutgoingAsyncCompletionCallback和AsyncResult,AsyncResult是一个提供给上层访问的接口,基本与回调框架无关。我们将无关的繁枝冗节去除,只留下回调框架相关的方法接口以及依赖关系,如下图:
白色的类是我们上面Future类图中的成员,并且借用OC对类(@interface)的分类声明,我们将一个类的方法按功能聚集成一个分类。
分类中的方法名与我们本篇开始提到的回调接口sent,response和exception相关。相关的方法有
sent,sentImpl,invokeSent,invokeSentAsync,handleSent, handleInvokeSent ;
response,responseImpl,invokeResponse,invokeResponseAsync,handleResponse,handleInvokeResponse ;
exception,exceptionImpl,invokeException,invokeExceptionAsync, handleException,handleInvokeException ;
这些函数的命名相当迷惑人,如果你不知道它们的真正作用又没有文档的话。sent,response和exception分别必须调用它们对应的(sent|response|exception)Impl版本,它们的Impl版本实质在调用它们各自对应的handle(Sent|Response|Exception)方法。但是这三组方法,根本不是回调处理,而只是三组查询方法,它们都返回一个bool值,表示是否支持回调。剩下的方法则都带invoke词根,但是它与AsyncResult的invoke并没有关系,而是真正要调用事件回调。invokeSent,invokeResponse和invokeException分别依赖调用handleInvokeSent,handleInvokeResponse和handleInvokeException,它们就是最后委托上层回调的地方。至于invokeSentAsync,invokeResponseAsync和invokeExceptionAsync则是将invokeSent,invokeResponse和invokeException方法委托给嵌套类,dispatch(和OC的GCD中语义一样)到线程池异步执行。invokeSent,invokeResponse和invokeException方法集是枢纽点,上面图用红字显示。当执行一次带回调需要的同步远程调用,就有一条最小的依赖路径,即上图中红色类和红字类,ConnectionI和OutgoingAsyncBase。网络服务层的Connection在调用io事件时,回调Future(OutgoingAsyncBase,which is a AsyncResult)的回调处理,再通过OutgoingAsyncCompletionCallback接口服务委托回调到上层。