美文网首页iOSiOS架构方案
03--(三)MVVM项目结构解读

03--(三)MVVM项目结构解读

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

    一、概述

    每个人的开发风格都不相同,于是在交接其他人的代码的时候,就要去熟悉别人的开发习惯,阅读别人的思维模式,这个过程的体验想必都深有体会。如果有一种固定的开发模板,每个人都只需要按照这个模板填写代码,尽管开发风格迥异,但仍能最快找到约定的代码。ProtocolChain刚好可以做到这一点,它只解决各个节点之间消息传递的问题,所以要使用它就必须要先构造出一个树形结构的架构,而树形结构的构造就需要参照特定的模板。

    1. 标准的MVVM项目依赖树,如下图;
    2. 固定的结构可以使用模板代码;
    3. 固定的模板便于代码的审查;
    4. 单一的职责更容易维护、组合、扩展;
    模块结构.png

    整体原则

    1. 下层对上层提供依赖;
    2. 右侧对左侧提供依赖;
    项目结构

    职责说明

    Dependency(依赖注入层)

    1、作用:返回模块的入口ViewController,内部完成ViewModel、DataStore的依赖注入;
    2、说明:除了View的创建,Controller、ViewModel、DataStore、Model等职责的创建和装配。譬如外部传进来的初始数据,可能需要DataStore或Model来接受,外部传入的Block,可能需要ViewModel来接受。依赖注入的方式可以隔离业务逻辑与视图层,对复用、替换、扩展都会带来非常友好的支持。
    3、实现案例:

    • dataStore接受并存储外部传入的参数
    • dataStore初始化viewModel,确保模块内数据流的唯一性
    • viewModel初始化控制器,确保逻辑处理的唯一性
    + (UIViewController *)createMVVMProViewController:(NSString *)testName
                                                model:(nonnull id<XYMVVMProDataModel>)testModel
                                                block:(nonnull void (^)(NSString * _Nonnull))testBlock {
        XYMVVMProDataStore *dataStore = [[XYMVVMProDataStore alloc] initWithModel1:testModel];
        XYMVVMProViewModel *viewModel = [[XYMVVMProViewModel alloc] initWithDataStore:dataStore block:testBlock];
        XYMVVMProViewController *vc = [[XYMVVMProViewController alloc] initWithViewModel:viewModel];
        return vc;
    }
    

    4、调用案例
    一般来说,模块外的接入者并不需要知道模块控制器叫啥名字,也不需要知道DataStore、ViewModel分别需要什么样的参数,只需要关注依赖注入层提供的参数列表,而拿到的也仅仅是一个纯粹的UIViewController,可以打开这个模块即可。

    UIViewController *vc = [XYMVVMProDependencyFactory createMVVMProViewController:@"张三"
                                                                                 model:model
                                                                                 block:^(NSString * _Nonnull desc) {
            NSLog(@"%@", desc);
    }];
    [self.navigationController pushViewController:vc animated:YES];
    

    Controller(核心控制层)

    1、系统提供的视图生命周期、导航、状态栏相关权限;

    - (instancetype)init;
    - (void)dealloc;
    
    - (void)loadView;
    - (void)viewDidLoad;
    
    - (void)viewWillAppear:(BOOL)animated;
    - (void)viewDidAppear:(BOOL)animated; 
    - (void)viewWillDisappear:(BOOL)animated;
    - (void)viewDidDisappear:(BOOL)animated; 
    

    2、作为树的根节点,关联View与ViewModel
    这里命名actionResponder事件响应者,表示指定View的事件响应者为ViewModel,数据流方向是从View->ViewModel,从模板化

    self.proView.actionResponder = self.proViewModel;
    self.proView.proView1.actionResponder = self.proViewModel.proViewModel1;
    self.proView.proView2.actionResponder = self.proViewModel.proViewModel2;
    

    3、构造协议响应链,分发消息

    1. 按照下面的协议响应链的格式,可以将消息发送到树的任何一个节点;
    2. 可以将所有点击消息都发送给traceServertraceServer在初始化的时候可以指定dataStore,如此便可将埋点的代码都隔离到traceServer中;

    协议响应链一

    1. 指定@protocol(XYMVVMProActionRespondable)的第一响应者为self.proViewModel,需要实现协议中的所有方法,并都是第一响应者;
    2. 为协议中的方法@selector(mvvmViewActionCallback)构造响应链,后续响应链为self
    3. 为协议中的方法@selector(mvvmViewActionCallback)构造响应链,后续响应链为self. traceServer
    self.bind(@protocol(XYMVVMProActionRespondable), self.proViewModel)
        .link(self, @selector(mvvmViewActionCallback))
        .link(self.traceServer, @selector(mvvmViewActionCallback))
        .close();
    

    从日志中可以看出方法mvvmViewActionCallback的响应链为:

    XYMVVMProViewModel -> XYMVVMProViewController -> XYMVVMProTraceServer

    [ProtocolChain] <XYMVVMProActionRespondable>    【XYMVVMProViewModel】
        [0][ResponderChain][mvvmViewActionCallback] 
            -> XYMVVMProViewController
            -> XYMVVMProTraceServer
    

    协议响应链二

    1. 指定@protocol(XYMVVMProActionRespondable1)的第一响应者为self.proViewModel.proViewModel1,需要实现协议中的所有方法,并都是第一响应者;
    2. 为协议中的方法@selector(mvvmView1ActionCallback)构造响应链,后续响应链为self
    3. 为协议中的方法@selector(mvvmView1ActionCallback)构造响应链,后续响应链为self. traceServer
    self.bind(@protocol(XYMVVMProActionRespondable1), self.proViewModel.proViewModel1)
        .link(self, @selector(mvvmView1ActionCallback))
        .link(self.traceServer, @selector(mvvmView1ActionCallback))
        .close();
    

    从日志中可以看出方法mvvmView1ActionCallback的响应链为:

    XYMVVMProViewModel -> XYMVVMProViewController -> XYMVVMProTraceServer

    [ProtocolChain] <XYMVVMProActionRespondable1>   【XYMVVMProViewModel1】
        [0][ResponderChain][mvvmView1ActionCallback]    
            -> XYMVVMProViewController
            -> XYMVVMProTraceServer
    

    协议响应链三

    1. 指定@protocol(XYMVVMProActionRespondable2)的第一响应者为self.proViewModel.proViewModel2,需要实现协议中的所有方法,并都是第一响应者;
    2. 为协议中的方法@selector(mvvmView2ActionCallback)构造响应链,后续响应链为self
    3. 为协议中的方法@selector(mvvmView2ActionCallback)构造响应链,后续响应链为self. traceServer
    self.bind(@protocol(XYMVVMProActionRespondable2), self.proViewModel.proViewModel2)
        .link(self, @selector(mvvmView2ActionCallback))
        .link(self.traceServer, @selector(mvvmView2ActionCallback))
        .close();
    

    从日志中可以看出方法mvvmView2ActionCallback的响应链为:

    XYMVVMProViewModel -> XYMVVMProViewController -> XYMVVMProTraceServer

    [ProtocolChain] <XYMVVMProActionRespondable2>   【XYMVVMProViewModel2】
        [0][ResponderChain][mvvmView2ActionCallback]    
            -> XYMVVMProViewController
            -> XYMVVMProTraceServer
    

    思考,ViewModel如何将消息发送给View?

    View(视图显示层)

    1、头文件结构

    1、非叶子节点

    @property (nonatomic, weak) id<XYMVVMProActionRespondable> actionResponder;
    @property (nonatomic, strong, readonly) XYMVVMProView1 *proView1;
    @property (nonatomic, strong, readonly) XYMVVMProView2 *proView2;
    
    • actionResponder:事件响应者,用来将View里面的事件传递出去;
    • subviews:子树节点,提供Controller访问路径;

    2、叶子节点

    @property (nonatomic, weak) id<XYMVVMProActionRespondable1> actionResponder;
    

    只需要提供actionResponder:事件响应者,为视图数据的输出以及输入提供通道即可。

    2、实现文件结构

    1、视图初始化

    • 私有变量
    • 初始化init
    • 懒加载(针对上述的私有变量)

    这三部分是最基础的视图显示需要的步骤,而如果视图本身需要修改,例如文本、颜色、位置大小等属性的变化,view便需要一个异步操作的注入,这里通过Block来实现。

    • setActionResponder:重写事件响应者,实现事件响应者的闭包,完成异步操作的注入。而_actionResponder也并不是一个固定的类型,而是遵循特定协议的抽象类型,所以View在这个结构中并没有任何实体的依赖。
    - (void)setActionResponder:(id<XYMVVMProActionRespondable1>)actionResponder {
        _actionResponder = actionResponder;
        
        __weak typeof(self) weakSelf = self;
        _actionResponder.updateColorBlock = ^(UIColor * _Nonnull color) {
            weakSelf.backgroundColor = color;
        };
        
        _actionResponder.getColorBlock = ^UIColor * _Nonnull{
            return self.backgroundColor;
        };
    }
    

    ViewModel(逻辑处理层)

    主要作用
    1. 业务逻辑的处理,数据获取的发起、数据流转、视图刷新、视图跳转等业务逻辑;
    2. ViewModel同样也是树的结构,对于非叶子节点的ViewModel下,会挂多个子ViewModel,可以集中处理多个子ViewModel的共同逻辑,可以处理数据需要在子ViewModel中多次来回流转的逻辑(隔离子ViewModel之间的直接沟通);
    3. 子ViewModel,不是所有的业务逻辑都需要提升到父ViewModel中去处理,往往在子ViewModel中即可处理完成,形成闭环;
    关联职责
    1. View:作为View的事件响应者,需要处理View传出来的事件,在合适的地方调用Block,触发View的异步刷新;
    2. DataStore:作为ViewModel的初始化属性,一般设置为只读属性,在各个ViewModel之间传递,数据的存储以及部分数据的组装;
    3. Server:服务层,往往提供单一的服务。例如网络请求Server提供数据的获取和部分数据组装的能力,埋点Server提供埋点的能力,图片处理Server提供图片的合成、裁剪、渲染等能力;
    文件结构
    1. 一般不直接存储数据,只管理数据;
    2. 所以注入的属性都需要设置成只读属性,例如从Dependency层注入的block、dataStore,所有的子ViewModel;
    3. 实现文件中往往会有多个协议的方法,只需要按需添加方法,在Controller中link即可,并不需要增加属性;
    @interface XYMVVMProViewModel : NSObject<XYMVVMProActionRespondable>
    @property (nonatomic, strong, readonly) XYMVVMProViewModel1 *proViewModel1;
    @property (nonatomic, strong, readonly) XYMVVMProViewModel2 *proViewModel2;
    @property (nonatomic, strong, readonly) XYMVVMProDataStore *dataStore;
    @property (nonatomic, copy, readonly)   void (^testBlock)(NSString * _Nonnull);
    - (instancetype)initWithDataStore:(XYMVVMProDataStore *)dataStore block:(nonnull void (^)(NSString * _Nonnull))testBlock;
    @end
    
    与MVC的对比

    传统的MVC(胖Model)往往会把数据的获取、组装、存储都放在Model中,这样会增加对Model的理解困难,Model更重要的职责是其所描述的特征。尽管如此,Controller仍然避免不了异常的庞大,所有View的事件处理,View的刷新、数据的流转等职责仍然很繁重。不合理的MVC往往会直接把逻辑处理放在View里面,最后的最后,你中有我我中有你,难舍难分!

    上面介绍的结构,不是从某种特定架构出发,而是从单一职责出发

    • 每个职责都只负责自己的功能,可以为其他任何模块提供服务,也可以通过依赖注入的方法为任何模块所用;
    • 同一职责也会按照某种规则尽量细分,细分到可以随意组装的地步;

    在满足上述前提的条件下,只需要解决几个问题:

    1. ActionOutput:View如何将事件传递出去;
    2. DataInput:Data如何渲染到View;
    3. DataStream:Data是如何流转(DataStore、NetServer、ViewModel);

    ActionOutput和DataInput比较简单,都是一对一的逻辑。DataStream数据流往往涉及多个职责之间的共同协作,数据的获取、组装、存储,因此需要有一个通道来流转这些数据。如此便可以通过ProtocolChain来进行分发。

    DataStore(数据存储层)

    • 作用:所有数据的存储
    • 特性:模块内的数据存储中心,往往需要确保唯一性

    1、头文件结构

    @property (nonatomic, strong, readonly) id<XYMVVMProDataModel> model1;
    @property (nonatomic, strong, readonly) XYMVVMProModel2 *model2;
    - (instancetype)initWithModel1:(id<XYMVVMProDataModel>)model1;
    
    • 外部传进来的数据,需要放在指定初始化方法中,并设置为只读属性:- (instancetype)initWithModel1:(id<XYMVVMProDataModel>)model1

    • 内部生成的数据,尽量定义成只读属性:@property (nonatomic, strong, readonly) XYMVVMProModel2 *model2;

    • DataStore的初始化,一般会放在Dependency层中,注入给ViewModel,以ViewModel作为根节点的树下面的所有节点都需要接受这个DataStore,来保证模块内唯一性;

    XYMVVMProViewModel:根ViewModel

    @interface XYMVVMProViewModel : NSObject<XYMVVMProActionRespondable>
    @property (nonatomic, strong, readonly) XYMVVMProViewModel1 *proViewModel1;
    @property (nonatomic, strong, readonly) XYMVVMProViewModel2 *proViewModel2;
    @property (nonatomic, strong, readonly) XYMVVMProDataStore *dataStore;
    @property (nonatomic, copy, readonly)   void (^testBlock)(NSString * _Nonnull);
    - (instancetype)initWithDataStore:(XYMVVMProDataStore *)dataStore block:(nonnull void (^)(NSString * _Nonnull))testBlock;
    @end
    

    XYMVVMProViewModel1:子ViewModel

    @interface XYMVVMProViewModel1 : NSObject<XYMVVMProActionRespondable1>
    @property (nonatomic, strong, readonly) XYMVVMProDataStore *dataStore;
    - (instancetype)initWithDataStore:(XYMVVMProDataStore *)dataStore;
    @end
    

    XYMVVMProViewModel2:子ViewModel

    @interface XYMVVMProViewModel2 : NSObject<XYMVVMProActionRespondable2>
    @property (nonatomic, strong, readonly) XYMVVMProDataStore *dataStore;
    - (instancetype)initWithDataStore:(XYMVVMProDataStore *)dataStore;
    @end
    

    2、实现文件结构
    初始化相关的方法往往可以做成固定的模板

    1、私有可读可写属性的定义;
    2、指定初始化方法的实现;
    3、私有属性的懒加载;
    

    除了对数据的存储,DataStore也会分担部分数据的组装。用好getter方法,可以对数据的变化更进一步的抽象。例如,可以对ViewModel增加只读属性:
    @property (nonatomic, assign, readonly) BOOL isGetUserInfo;
    如此便可以将业务的条件封装在DataStore中,调用方并不需要关心内部是根据什么判断的,判断的那些条件。这里仅仅是作为一个简单的🌰,如果有更加复杂的条件,不妨考虑策略模式,可能会提供更多的选择。

    - (BOOL)isGetUserInfo {
        return (self.model1 && self.model1.name && self.model1.address);
    }
    

    Model(数据描述层)

    Model最主要作用是对数据的描述,换成代码的语言,指类的公有属性,指示数据的含义。

    Model一般也会作为数据传输的单元在多个模块之间传递。通常情况下,定义在A模块中的ModelA不希望被B模块直接引用(也可能是因为不同库封装的问题)、或者B模块可以引用到ModelA,但是希望对ModelA进行一些修改。这种case是非常场景的,一旦ModelA同时兼容了A模块和B模块的某种特性,那么ModelA便被污染了。

    万能的解决这种问题的方法是中间层,“所有的计算机问题都可以用中间层解决”,这是计算机大佬提出来的观点,这里就不做过多的评价了。

    常规的解决思路是数据转换,在A模块中奖ModelA转成字典DictionaryA,B模块接受了DictionaryA,并转成成自己的数据类型。在C/S模式下,A模块作为Server端,B模块作为Client端,类似于客户-服务端的通信模式,传输的都是json数据。这种方式自然能解决数据交互的问题,但同样的数据需要进行两次转换才能被使用,而且是面向字符串约定,任何一端的修改都会造成数据解析失败的问题,且问题还不会立马被发现,需要在运行时才能确定问题所在。

    优雅的解决思路是面向协议,A模块并不直接传递ModelA,而是传递一个抽象类型id<ModelA>,而B模块接收到id<ModelA>,如果不需要修改,可直接使用,若要修改,完全可以用自己模块内的数据接收:ModelB<ModelA>。这个时候也仅仅是A模块和B模块同时对抽象<ModelA>的依赖,并不会导致某个实例对象被污染。

    抽象类型id<ModelA>往往也是存在依赖的,使用场景也存在一定局限性,适用于同一个项目、组件内部的模块之间的调用。如果是跨组件的调用,这种方式仍然要求调用方依赖提供方的某种抽象类型id<ModelA>,对组件解耦来说是不太友好的一种方式。要解决这个问题,其实也非常简单,在Target-Action体系中,我们只需要在Target实现Action方法中来调用Dependency层的方法即可,或者是在分类中提供接口支持,仍然可以保证模块内部的完整性。

    Server(服务提供层)

    • 低业务关联性
    • 独立可替换性
    • 操作复杂性

    举个🌰,基础框架提供的图片处理是:切圆角重设图片大小图片拼接三个基础的操作,而业务要去是,不同大小的图片要拼接成一张图片,且要切圆角。所以可以在ImageServer中提供一个新的API,兼容这三个操作。

    低业务关联,而不是完全不关联业务。只有在当前模块中才会需要有这种图片操作,因此是业务关联的。而这个API又可以脱离业务由其他任何地方调用,因此是低业务关联性的。

    独立可替换性,这个图片操作是完全独立,不依赖MVVM架构中的任何职责,内部实现也是完全不透明的,可能是CoreImage、也可能是BitMap、或者是某个图片处理框架。随着系统变更,也许会有更先进、高效的手段出现,到时候只需要更新内部的实现即可,丝毫不影响业务逻辑。

    操作复杂性,上面的操作如果放在ViewModel中,势必会造成ViewModel体积的变大,增加了一大堆非业务逻辑的代码,让本就复杂的代码更加雪上加霜。

    上面的一个例子是ImageServer,常见的Server还有网络相关的。

    NetServerMockServer,NetServer提供访问数据库的能力(也可以在这里进行部分数据的组装,返回加工好的数据)。然而,一个新的迭代开始阶段,后台仅仅提供了一份接口文档,当你需要的时候可能接口都没有通,程序员自然是有非常多的解决办法。本地搭建一个Mock服务,然后对每个接口都Mock数据并返回,这个要求稍微有点高,要能搭建其Mock服务且非常熟练Mock的API,除此之外,可能仍需耗费比较多的时间。另外一种使用在线的MockAPI,但前提是公司的网关没有限制,可以随意访问MockAPI,而对网络安全要求比较高的公司,可能还是需要手写数据来完成Mock,而NetServer中写好的数据解析的逻辑肯定是不会有人想要去打乱了,因此MockServer便站出来了,让NetServerMockServer同时遵循协议<xxxNetInterface>,在调用的地方并不直接使用NetServer类来创建对象,而是创建一个抽象类型id<xxxNetInterface>,因此可以在需要Mock数据的时候,将NetServer改成MockServer,在MockServer中返回手写的数据即可。

    TraceServer,业务代码中出现上百行的埋点代码,这无疑是一种灾难。而埋点的存在仅仅是在用户触发某个操作的时候记录一下而已,完全是应该要脱离业务的,埋点仅仅需要时机+数据即可

    • 事件响应
    • 某些内容

    上面介绍过了DataStore的职责,所有的数据都存储在这个对象中,所以我们可以在TraceServer的初始化中,将DataStore传递进去即可:

    - (XYMVVMProTraceServer *)traceServer {
        if (!_traceServer) {
            _traceServer = [[XYMVVMProTraceServer alloc] initWithDataStore:self.proViewModel.dataStore];
        }
        return _traceServer;
    }
    

    在介绍Controller时,已经将事件传递给了TraceServer,当TraceServer同时获取到时机+数据后,便可以从业务代码中隔离出来。

    self.bind(@protocol(XYMVVMProActionRespondable), self.proViewModel)
        .link(self, @selector(mvvmViewActionCallback))
        .link(self.traceServer, @selector(mvvmViewActionCallback))
        .close();
    
    self.bind(@protocol(XYMVVMProActionRespondable1), self.proViewModel.proViewModel1)
        .link(self, @selector(mvvmView1ActionCallback))
        .link(self.traceServer, @selector(mvvmView1ActionCallback))
        .close();
    
    self.bind(@protocol(XYMVVMProActionRespondable2), self.proViewModel.proViewModel2)
        .link(self, @selector(mvvmView2ActionCallback))
        .link(self.traceServer, @selector(mvvmView2ActionCallback))
        .close();
    

    还有更多的Server,满足上面三个特性,我们便可以将其抽离出来,以减少ViewModel的工作量,以封装某种变化,以提供某种扩展。

    Protocol(协议提供层)

    对协议比较好的分类应该是三种协议,分别对应上面介绍的:

    1. ActionOutput:事件输出协议
    2. DataInput:数据输入协议
    3. DataStream:数据流转协议

    因为block的特性,这里便定义了两种:事件响应协议数据模型协议

    xxxActionRespondable

    常规的命名规则后缀是:xxxDelegate,而Delegate这个词汇本身并无法表示任何含义,所以不太合适出现在当前架构中,我们用xxxActionRespondable代替,表示可响应的事件。而在View中定义的属性也不再使用delegate这里没有具体含义的命名,用actionResponder代替,表示事件响应者。例如:@property (nonatomic, weak) id<XYMVVMProActionRespondable1> actionResponder;。下面是一些事件响应协议定义的举例:

    @protocol XYMVVMProActionRespondable <NSObject>
    - (void)mvvmViewActionCallback;
    @property (nonatomic, copy) void (^hideView2Block)(BOOL isHidden);
    @end
    
    
    @protocol XYMVVMProActionRespondable1 <NSObject>
    - (void)mvvmView1ActionCallback;
    @property (nonatomic, copy) void (^updateColorBlock)(UIColor *color);
    @property (nonatomic, copy) UIColor *(^getColorBlock)();
    @end
    
    
    @protocol XYMVVMProActionRespondable2 <NSObject>
    - (void)mvvmView2ActionCallback;
    @end
    

    【同名冲突】众所周知,oc中没有命名空间这一概念,所以的差异都需要通过命名来直接区分,因此不同协议中定义相同的方法名便有可能造成歧义。

    @protocol XYMVVMProActionRespondable <NSObject>
    - (void)mvvmViewActionCallback;
    @end
    
    
    @protocol XYMVVMProActionRespondable1 <NSObject>
    - (void)mvvmViewActionCallback;
    @end
    

    上面的两个协议中都有mvvmViewActionCallback方法,当一个类同时遵循这两个协议的时候,便无法区分这个事件是从哪个对象发出来的,所以需要通过命名的方式区分开来。

    xxxModel

    在将Model层的时候,介绍过面向抽象所解决的问题,这里就不再累述,可以看下定义:

    @protocol XYMVVMProDataModel <NSObject>
    @property (nonatomic, copy, readonly) NSString *name;
    @property (nonatomic, copy, readonly) NSString *address;
    @end
    

    多个事件协议可能会放到一个文件中(具体执行需要按需斟酌),但数据协议必须要与事件协议分开,很多情况下,数据协议都需要随着Dependency层一起对外公开,例如:

    #import <Foundation/Foundation.h>
    #import "XYMVVMProDataModel.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface XYMVVMProDependencyFactory : NSObject
    + (UIViewController *)createMVVMProViewController:(NSString *)testName
                                                model:(nonnull id<XYMVVMProDataModel>)testModel
                                                block:(nonnull void (^)(NSString * _Nonnull))testBlock;
    @end
    
    NS_ASSUME_NONNULL_END
    

    block

    09-异步回调(block).png
    - (void)setActionResponder:(id<XYMVVMProActionRespondable1>)actionResponder {
        _actionResponder = actionResponder;
        
        __weak typeof(self) weakSelf = self;
        _actionResponder.updateColorBlock = ^(UIColor * _Nonnull color) {
            weakSelf.backgroundColor = color;
        };
        
        _actionResponder.getColorBlock = ^UIColor * _Nonnull{
            return self.backgroundColor;
        };
    }
    
    • Block定义(DEF):在xxxActionRespondable中声明;
    • Block实现(IMP):在setActionResponder时注入。按照我们对block的理解,block会捕获self(View)拷贝到堆中,在调用的时候便可以访问到View;
    • Block调用(SEL):在ViewModel遵循xxxActionRespondable协议时,便有了block的getter方法,但ViewModel中并没有block的变量,getter方法会返回一个空对象。这时需要使用:@synthesize合成一个实例变量,如下所示:
    @implementation XYMVVMProViewModel1
    @synthesize getColorBlock=_getColorBlock;
    @synthesize updateColorBlock=_updateColorBlock;
    

    因为block的调用是invoke(func),并非msg_send(),所以不需要依赖任何对象即可完成数据的异步刷新。


    源码地址

    【后记】一直在想着怎么不依赖响应式框架RAC来完成MVVM的数据绑定,也模仿RAC写过简单的MVVM框架,但相关类仍然需要依赖自定义的信号,才可以完成订阅。一旦框架出现某种不可修改的缺陷或没人维护的时候,在替换的时候需要修改的地方太多,存在的风险过大;基于消息转发实现了ProtocolHook能力;参照响应链的方式构造了协议响应链;结合RN中Redux的思想,将需要转发的事件都挂载在根节点controller上,需要消费的对象,在controller中接受事件即可;面向协议依赖是一种依赖抽象的思想,所以可以很好的拆分、替换、复用;因为是借助runtime的能力动态去转发,所以不会破坏任何类的原始结构,都是objc中最原始的用法。假如框架出现不可修改的缺陷或者没人维护时,也可以重新建立代理关系,即可完成消息链路的构造,并不会影响到业务逻辑的修改。

    相关文章

      网友评论

        本文标题:03--(三)MVVM项目结构解读

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