美文网首页
03--(二)ProtocolChain与MVVM

03--(二)ProtocolChain与MVVM

作者: 修_远 | 来源:发表于2021-09-16 16:38 被阅读0次

    一、通信解决思路

    Controller作为一个控制中心,如果所有的消息都会经过Controller,将事件的分发交给根节点,那么Controller就可以非常容易的找到树里面的任意一个节点,并将消息发送给该节点。

    1. 兄弟通信(链):将树形结构的转成一个单链表的链式结构,只要是在这个链上的对象都可以响应协议中的某个方法。消息传递链路就从:ViewModel1a -> ViewModel1 -> ViewModel1b 变成了:ViewModel1a -> ViewModel1b,而且不需要手动去设置代理,只要将ViewModel1b link 到协议链上即可
    06-兄弟通信(链).png
    1. 孙子通信(链):对于比较深的结构
      原始链路:ViewModel1a -> ViewModel1 -> ViewModel -> ViewModel2 -> ViewModel2b
      使用ProtocolChain:同样只需要把ViewModel2b link到协议链上即可
    07-孙子通信(链).png
    1. 逐级回调(链):对于更加复杂的场景,我们可以构造上述的协议链,协议响应链的思想和事件响应链比较类似,一个Controller中可以有多条协议链,但统一时间,只有一个协议链处于活跃状态。
    08-逐级回调(链).png
    1. 异步回调(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中的用户协议按钮时,BTViewModelviewModel都可以响应 @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源码

    四、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?

    1. 遵循协议<xyProtocolHookInvocation>
    2. 设置调用者:invocator
      完成这两步,即可收到protocolHook回调的forwardInvocation方法

    4.5 How Representer?

    当我们写一段代码时,就需要考虑这段代码将来是否会被废弃、将来是否需要移植,特别是在引用三方库的时候,更加要注意三方库的替换。所以经常会对三方库二次封装,以避免将来散弹式的修改。
    除此之外,我们仍然需要考虑在替换时的可行性已经工作量。
    1、不能修改业务逻辑
    2、不能修改数据结构
    ProtocolChain不修改任何数据结构,是借助runtime的能力,将消息动态发送给接受者。
    当ProtocolChain失效时,我们仍然可以通过最原始的方法,还原代理建立的过程,以确保消息的发送。

    4.6 How Reuse?

    Reuse View

    1. 视图添加

      1. 根据需求完成布局开发
      2. 添加到superView中
    2. 处理数据

      1. 添加协议的代理属性
      2. 调用协议中的方法
      3. 实现协议中block的实现

    Reuse ViewModel

    1. 遵循指定的协议
    2. 根据需求完成数据的处理
    3. 调用block完成数据的传递

    4.7 Why not Model?

    Model是一个实例对象,往往表示某种具体的数据模型,一般不会被外界所知道,外界也不需要直接依赖内部的Model,而通过Dictionary的方式转换,就会出现两个问题:

    1. 每次数据传递都需要来回转换两次;
    2. Dictionary的数据格式的嵌套不够直观,且容易出错;

    所以一般会定义数据模型的协议,只要外部传进来的数据遵循我们定义的协议,我们即可从这个数据模型中取指定的数据。这里的协议就像就像一个筛子的功能,过滤我们不需要的数据,只取数据模型中的某些必要字段,如果没有,可以在数据模型中实现某个getter方法,主动转成我们需要的数据。

    @protocol xxxModel <NSObject>
    - (NSString *)name;
    - (NSString *)address;
    - (int)age;
    @end
    

    注意事项

    同一模块,不同的协议,相同的方法名,尽量根据小模块区分开。在oc里面,同一个类遵循不同的协议,如果方法名是相同的,这个类是无法区分来源于哪个类的,这个在bind定义的地方也要说明,具体可以看代码。

    相关文章

      网友评论

          本文标题:03--(二)ProtocolChain与MVVM

          本文链接:https://www.haomeiwen.com/subject/khirgltx.html