一、通信解决思路
Controller作为一个控制中心,如果所有的消息都会经过Controller,将事件的分发交给根节点,那么Controller就可以非常容易的找到树里面的任意一个节点,并将消息发送给该节点。
- 兄弟通信(链):将树形结构的转成一个单链表的链式结构,只要是在这个链上的对象都可以响应协议中的某个方法。消息传递链路就从:
ViewModel1a -> ViewModel1 -> ViewModel1b
变成了:ViewModel1a -> ViewModel1b
,而且不需要手动去设置代理,只要将ViewModel1b
link 到协议链上即可
- 孙子通信(链):对于比较深的结构
原始链路:ViewModel1a -> ViewModel1 -> ViewModel -> ViewModel2 -> ViewModel2b
使用ProtocolChain:同样只需要把ViewModel2b
link到协议链上即可
- 逐级回调(链):对于更加复杂的场景,我们可以构造上述的协议链,协议响应链的思想和事件响应链比较类似,一个Controller中可以有多条协议链,但统一时间,只有一个协议链处于活跃状态。
- 异步回调(block):上面的操作仅仅是辅助我们将消息从View里面传递出来,属于用户交互这一块,而View本身作为一个对象,也是需要更新视图的,那么View也可以被link到协议链中,去响应其他View的操作。
如果ViewModel中网络请求的数据发生了改变,需要更新View,这个时候可以使用block来实现:
- block的定义:定义在协议中;
- block的实现:实现在View里面,具体是ViewModel的set方法中;
- block的调用:在ViewModel层的任何一个位置都可;
通过将block的IMP注入到View中,便实现的数据的异步回调。
09-异步回调(block).png二、ProtocolChain调用示例
self.loginView.viewModel = self.viewModel;
self.loginView.TFView.viewModel = self.viewModel.TFViewModel;
self.loginView.BTView.viewModel = self.viewModel.BTViewModel;
self.bind(@protocol(XYMVVMBottomViewDelegate), self.viewModel.BTViewModel)
.link(self.viewModel, @selector(bottomViewDidUserProtocol))
.link(self, @selector(bottomViewDidUserProtocol))
.close();
self.bind(@protocol(XYMVVMTextFieldViewDelegate), self.viewModel.TFViewModel)
.link(self.viewModel, @selector(textFieldViewAccountChanged:))
.link(self.viewModel, @selector(textFieldViewPasswordChanged:))
.link(self.viewModel, @selector(textFieldViewVericodeChanged:))
.close();
[self traverseProtocolChain];
1. 建立 view -- delegate -- viewModel
关系
self.loginView.viewModel = self.viewModel;
self.loginView.TFView.viewModel = self.viewModel.TFViewModel;
self.loginView.BTView.viewModel = self.viewModel.BTViewModel;
这一步非常重要,关系的建立参照二叉树中的对应关系。viewModel将会作为消息的第一响应者去响应事件,并且viewModel会遵循Protocol中的方法也会被hook,所以这一步的前提有两个:
1、建立 view -- delegate -- viewModel 关系
2、viewModel中实现delegate定义的所有方法
2、构造协议链
self.bind(@protocol(XYMVVMBottomViewDelegate), self.viewModel.BTViewModel)
.link(self.viewModel, @selector(bottomViewDidUserProtocol))
.link(self, @selector(bottomViewDidUserProtocol))
.close();
self.bind(@protocol(XYMVVMTextFieldViewDelegate), self.viewModel.TFViewModel)
.link(self.viewModel, @selector(textFieldViewAccountChanged:))
.link(self.viewModel, @selector(textFieldViewPasswordChanged:))
.link(self.viewModel, @selector(textFieldViewVericodeChanged:))
.close();
bind
表示协议链构造开始
-
@protocol(XYMVVMBottomViewDelegate)
:指定协议 -
self.viewModel.BTViewModel
:指定协议的第一响应者对象,第一响应者会响应协议中的所有方法
link
表示将协议中的某个方法分发给某个对象
-
self.viewModel
:响应指定协议中的某个方法 -
@selector(bottomViewDidUserProtocol)
:指定方法,只能响应指定的某个方法
完成这两步之后,当点击BTView
中的用户协议按钮时,BTViewModel
和viewModel
都可以响应 @selector(bottomViewDidUserProtocol)
[self traverseProtocolChain]; 查看协议响应链结构
[Controller] 【XYMVVMViewController】
[ProtocolChain] <XYMVVMBottomViewDelegate> 【XYMVVMBottomViewModel】
[0][ResponderChain][bottomViewDidUserProtocol]
-> XYMVVMViewModel
-> XYMVVMViewController
[ProtocolChain] <XYMVVMTextFieldViewDelegate> 【XYMVVMTextFieldViewModel】
[0][ResponderChain][textFieldViewVericodeChanged:]
-> XYMVVMViewModel
[1][ResponderChain][textFieldViewPasswordChanged:]
-> XYMVVMViewModel
[2][ResponderChain][textFieldViewAccountChanged:]
-> XYMVVMViewModel
- [Controller] :指定当前控制器为【XYMVVMViewController】
- [ProtocolChain]:有两条协议响应链
- [ResponderChain]:表示某个方法的响应链
协议<XYMVVMBottomViewDelegate> 第一响应者为:【XYMVVMBottomViewModel】,
协议<XYMVVMBottomViewDelegate>中[bottomViewDidUserProtocol] 的方法响应链为:-> XYMVVMViewModel -> XYMVVMViewController
,第一个节点前也有一个 ->
,表示它并不是真正的第一个节点,方法响应链上的所有节点都是在第一响应者之后。
使用ProtocolChain就可以非常完美的解决各个节点之间的通信问题了。
三、ProtocolChain的实现原理
四、ProtocolChain中的角色说明
4.1 Why Controller?
Controller作为一个核心控制者,Controller可以获取到树结构上的每一个节点,并完成View和ViewModel之间的绑定。
如果将协议链的控制权交给View或者ViewModel,必然需要增加一层回调才能拿到Controller,让View或者ViewModel去持有Controller是一个非常不好的设计。
所以Controller也是协议链的一个非常好的控制者,调度View和ViewModel之间的关系。具体的业务逻辑放在ViewModel中完成即可。
4.2 How Delegator?
代理从程序执行的流程来看,是一种不好的设计,因为代理会让一个顺序执行的流程产生分支,事件(数据)流转的逻辑变得复杂。而且增加一个代理会改变类的结构,增加对类的理解难度。
但从业务角度来看,代理是一个非常好的设计,提供跨角色通信的能力,让两个不同层级的类有了通信的可能。
4.3 Why not hook block?
Block是一个能捕获上下文的闭包,block的实现和调用本身就可以分离,让block的实现在View内部,让block的调用在ViewModel的任何业务代码中。block的调用方式是函数指针的调用 invoke(func)
,而不是msg_send的方法,不需要消息的接受者,所以不用一层一层的消息传递。
4.4 How does custom forwardInvocation: In hookObj?
- 遵循协议<xyProtocolHookInvocation>
- 设置调用者:invocator
完成这两步,即可收到protocolHook回调的forwardInvocation方法
4.5 How Representer?
当我们写一段代码时,就需要考虑这段代码将来是否会被废弃、将来是否需要移植,特别是在引用三方库的时候,更加要注意三方库的替换。所以经常会对三方库二次封装,以避免将来散弹式的修改。
除此之外,我们仍然需要考虑在替换时的可行性已经工作量。
1、不能修改业务逻辑
2、不能修改数据结构
ProtocolChain不修改任何数据结构,是借助runtime的能力,将消息动态发送给接受者。
当ProtocolChain失效时,我们仍然可以通过最原始的方法,还原代理建立的过程,以确保消息的发送。
4.6 How Reuse?
Reuse View
-
视图添加
- 根据需求完成布局开发
- 添加到superView中
-
处理数据
- 添加协议的代理属性
- 调用协议中的方法
- 实现协议中block的实现
Reuse ViewModel
- 遵循指定的协议
- 根据需求完成数据的处理
- 调用block完成数据的传递
4.7 Why not Model?
Model是一个实例对象,往往表示某种具体的数据模型,一般不会被外界所知道,外界也不需要直接依赖内部的Model,而通过Dictionary的方式转换,就会出现两个问题:
- 每次数据传递都需要来回转换两次;
- Dictionary的数据格式的嵌套不够直观,且容易出错;
所以一般会定义数据模型的协议,只要外部传进来的数据遵循我们定义的协议,我们即可从这个数据模型中取指定的数据。这里的协议就像就像一个筛子的功能,过滤我们不需要的数据,只取数据模型中的某些必要字段,如果没有,可以在数据模型中实现某个getter方法,主动转成我们需要的数据。
@protocol xxxModel <NSObject>
- (NSString *)name;
- (NSString *)address;
- (int)age;
@end
注意事项
同一模块,不同的协议,相同的方法名,尽量根据小模块区分开。在oc里面,同一个类遵循不同的协议,如果方法名是相同的,这个类是无法区分来源于哪个类的,这个在bind定义的地方也要说明,具体可以看代码。
网友评论