MVVM和ReactiveObjC实践2018

作者: 勇往直前888 | 来源:发表于2018-10-08 17:38 被阅读137次

尝试在工程中使用MVVMReactiveObjC
引入MVVM是为了将界面和逻辑分开,将界面接口,比如label.text,转化为相应view model的数据属性接口。
引入ReactiveObjC主要是为了优雅地进行Controllerview model之间的双向绑定。
关于ReactiveObjC,这篇文章很有用:
iOS开发之ReactiveCocoa下的MVVM

目标页面

登录模块是基础模块之一。

  • 以验证码方式登录
企业微信截图_055f0b32-c210-4fec-8b5a-5184660c26ad.png
  • 以账号密码方式登录
企业微信截图_38ced2ef-74ff-4b01-a311-47a58bbb1715.png
  • 两种登录方式可以切换,在同一个界面。并且两种方式的共同点很少。

  • 当前的阶段:需求分析和页面交互已经评审通过了,但是后台接口还没有出来。

实现方式

  • 可以代码写界面,这个无话可说。只是界面代码真的很无聊,所以不选。

  • 用一套界面实现,一个组件会充当两种功能,比如同一个输入框,一会儿是手机号,一会是账号,逻辑比较复杂。这样做,还不如直接代码写界面。

  • 这个页面,上面部分是固定的,下面部分可变。可以考虑将下面部分分成两个位置重叠的view。这个方案比上面两个都好多了,不过界面重合在一起,看不清楚,故事版所见即所得的优势发挥不出来。

  • 引入Container View将重叠的部分平铺开来,感觉会好很多。 布局之后的样子大概是这样的:

企业微信截图_b7237bbf-a3c9-4fce-a8bb-d166224c62e3.png
  • 基本上已经很像了,只是现在还不能切换。剩下的就需要代码来动态控制了。

  • 引入Container View之后,复用级别就从view改成了controller,形成12子三个controller。和子view类似,子controller可以由父controller持有,从而建立相互之间的关系。

#import "KJTPasswordLoginChildViewController.h"
#import "KJTCodeLoginChildViewController.h"

@interface KJTLoginViewController ()

// child controller
@property (strong, nonatomic) KJTPasswordLoginChildViewController *passwordController;
@property (strong, nonatomic) KJTCodeLoginChildViewController *codeController;

@end

#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
    if ([segue.identifier isEqualToString:@"toPassword"]) {
        self.passwordController = segue.destinationViewController;
    }
    if ([segue.identifier isEqualToString:@"toCode"]) {
        self.codeController = segue.destinationViewController;
    }
}

引入MVVM

  • 引入MVVM会增加文件数量,这是不好的地方。

  • 将界面和逻辑分开,给controller减负,这是好的地方。

  • 将界面元素接口,转化为view model的数据属性接口,这是好的地方。

  • 现在后台接口文档还没出来,接口的名字和字段都不知道,引入一个view model,就可以把整个交互逻辑串起来了,这是好的地方。

  • 等以后接口文档好了,接口名字和接口字段定了,只要做一下接口字段到view model的属性名字映射就可以了,界面不用改动。这种解耦的方式,很适合现在的场景。

结论:综合考虑,对于这个登录页面,在当前的场景下,引入MVVM是有利的。

文件结构

  • 在交互图上,是一个界面,只是通过类似的tab切换,是界面重合,导致开发复杂化。为了很好地利用故事版所见即所得的优势,我们用3controller来描述这一个界面。

  • 并没有规定说一个controller要配一个独立的view model。虽然是3个不同的controller,但是实际上,可以把他们看做是一个。对于这3个强关联的controller,完全可以共用一个view model

  • 将上面所讲的两个观点通过文件夹结构来提现,大概是这个样子的:

企业微信截图_b45e7e8f-29ef-49fc-a694-de93a8dd0ce1.png

引入ReactiveObjC

  • ReactiveObjC是大名鼎鼎的Reactive CocoaObject-C版本

  • 一提到ReactiveObjC,就提到函数式编程,说是虽然效果好,但是学习曲线很陡峭,让很多人陷入纠结。其实,ReactiveObjC不需要函数编程,也是能够使用的。

  • ReactiveObjC把所有的交互方式都统一成了信号,感觉很高深的样子。其实想点击相应,消息什么的,原生的API已经很好用,并需要转换。

  • 如果不用ReactiveObjC,那么从view model来更新界面这一块就麻烦。首先要提供一个类似updateInterface之类的函数,然后,在需要改变界面的时候调用,很繁琐

  • 如果用ReactiveObjC,那么可以建立controllerview model之间的属性绑定,不需要到处调用updateInterface,感觉好多了。

  • 在这里,只需要用到RACRACObserve,真的很简单。

结论:引入ReactiveObjC,建立controllerview model之间的属性绑定,当view model改变的时候,可以优雅地在界面上提现出来。

View Model定义

  • view model是用来描述界面的,相当于界面的接口。这里,我们只用来描述父controller

  • 这里的界面元素可以抽象为以下几种:

  1. 文字的颜色,选中是蓝色的,不选中是黑色的;
  2. 指示线的显示和隐藏;
  3. 代表子controllercontainer view的显示和隐藏;
  4. 当前用户选择了那种登录方式;
  • 通过数据属性,来描述上面提到的几点界面特征,定义如下:
typedef NS_ENUM(NSInteger,KJTLoginType) {
    KJTLoginTypeCode = 1,
    KJTLoginTypePassword = 2,
};

@interface KJTLoginViewModel : NSObject

/*
 *  KJTLoginViewController
 */

// 登录方式
@property (assign, nonatomic) KJTLoginType loginType;

// 验证码标签颜色
@property (strong, nonatomic) UIColor *codeLabelColor;
// 隐藏验证码标签横线
@property (assign, nonatomic) BOOL hideCodeLine;
// 隐藏验证码登录容器
@property (assign, nonatomic) BOOL hideCodeContainer;

// 账号密码标签颜色
@property (strong, nonatomic) UIColor *passwordLabelColor;
// 隐藏账号密码标签横线
@property (assign, nonatomic) BOOL hidePasswordLine;
// 隐藏账号密码登录容器
@property (assign, nonatomic) BOOL hidePasswordContainer;

/*
 *  KJTPasswordLoginChildViewController
 */


/*
 *  KJTCodeLoginChildViewController
 */

@end
  • 登录方式和其他的属性是有关系的,所以在创建view model的时候就可以将这种关系固定下来。这里使用ReactiveObjC会显得非常优雅。

  • 建立了登录方式和其他属性之间的关系之后,默认值就非常方便了,只要设置登录方式一项就可以了,其他的,自然有ReactiveObjC对应设置,非常方便。

  • 引入ReactiveObjC之后,view model的代码可以写得非常优雅:

#import "KJTLoginViewModel.h"

@implementation KJTLoginViewModel

#pragma mark - life cycle
- (instancetype)init {
    self = [super init];
    if (self) {
        [self bindLoginType];
        [self setDefaultValue];
    }
    return self;
}

#pragma mark - private
- (void)bindLoginType {
    [RACObserve(self, loginType) subscribeNext:^(id  _Nullable x) {
        KJTLoginType loginType = (KJTLoginType)[x integerValue];
        switch (loginType) {
            case KJTLoginTypeCode:
                self.codeLabelColor = kBlueColor;
                self.hideCodeLine = NO;
                self.hideCodeContainer = NO;
                self.passwordLabelColor = kBlackColor;
                self.hidePasswordLine = YES;
                self.hidePasswordContainer = YES;
                break;
            case KJTLoginTypePassword:
                self.codeLabelColor = kBlackColor;
                self.hideCodeLine = YES;
                self.hideCodeContainer = YES;
                self.passwordLabelColor = kBlueColor;
                self.hidePasswordLine = NO;
                self.hidePasswordContainer = NO;
                break;
            default:
                break;
        }
    }];
}

- (void)setDefaultValue {
    self.loginType = KJTLoginTypePassword;
}

@end

控制器属性

  • 输出口:故事版只能表达静态页面,可变的动态页面,需要拉输出口到controller中,进行动态控制。

  • view model:作为controller的一个属性成员,为controller处理交互逻辑,页面逻辑方面的工作。

  • 属性的定义大概是这个样子的:

@interface KJTLoginViewController ()
// 验证码登录
@property (weak, nonatomic) IBOutlet UILabel *codeLabel;
@property (weak, nonatomic) IBOutlet UIView *codeLine;
@property (weak, nonatomic) IBOutlet UIView *codeContainer;
// 账号密码登录
@property (weak, nonatomic) IBOutlet UILabel *passwordLabel;
@property (weak, nonatomic) IBOutlet UIView *passwordLine;
@property (weak, nonatomic) IBOutlet UIView *passwordContainer;
// view model
@property (strong, nonatomic) KJTLoginViewModel *viewModel;

@end

绑定View Model

view model的属性变化要反映在界面上,那么就需要建立代表界面的输出口和view model属性之间的绑定工作。有ReactiveObjC的帮助,这将会非常简单:

#pragma mark - private
- (void)bindViewModel {
    self.viewModel = [[KJTLoginViewModel alloc] init];
    RAC(self.codeLabel, textColor) = RACObserve(self.viewModel, codeLabelColor);
    RAC(self.codeLine, hidden) = RACObserve(self.viewModel, hideCodeLine);
    RAC(self.codeContainer, hidden) = RACObserve(self.viewModel, hideCodeContainer);
    RAC(self.passwordLabel, textColor) = RACObserve(self.viewModel, passwordLabelColor);
    RAC(self.passwordLine, hidden) = RACObserve(self.viewModel, hidePasswordLine);
    RAC(self.passwordContainer, hidden) = RACObserve(self.viewModel, hidePasswordContainer);
}

登录方式切换

由于界面的元素输出口已经和view model的属性进行了绑定; view model的其他属性已经和登录方式属性进行了绑定。所以,当用户切换登录方式时,只要修改view model的登录方式属性loginType就可以,简单易懂:

- (IBAction)codeButtonTouched:(id)sender {
    self.viewModel.loginType = KJTLoginTypeCode;
}

- (IBAction)passwordButtonTouched:(id)sender {
    self.viewModel.loginType = KJTLoginTypePassword;
}

运行效果

  • 由于view model中的默认值是以“账号密码方式登录”,所以默认界面是这样的:
- (void)setDefaultValue {
    self.loginType = KJTLoginTypePassword;
}
企业微信截图_8ed75d18-9d81-4481-a48c-212b716dda68.png
  • 切换到“验证码方式登录”,界面是这样的:
企业微信截图_43323917-8421-40f4-9317-2e832ca2a294.png

小结: 引入ReactiveObjC之后,以前相对比较繁琐的“tab切换”页面,可以非常简洁的实现。

相关文章

  • MVVM和ReactiveObjC实践2018

    尝试在工程中使用MVVM和ReactiveObjC。引入MVVM是为了将界面和逻辑分开,将界面接口,比如label...

  • iOS ReactiveObjC MVVM实践

    本文结合ReactiveObjC使用,搭建个简单的mvvm架构的程序。 1.什么是MVVM架构? MVVM是Mod...

  • IOS RAC实践

    前言 RAC使用-->IOS RAC使用 -- ReactiveObjC 本文使用RAC+MVVM来模拟用户登录 ...

  • 3.mvc和mvvm的区别和使用

    1.mvc和mvvm的由来 2.mvvm的概念 3.mvc和mvvm的区别 4.MVVM的实践 1.model层的...

  • MVVM架构的一次实践

    MVVM架构的一次实践 MVVM架构的一次实践

  • 独家记忆 | Jetpack MVVM 高频提问和解答

    很高兴见到你! 我是《Jetpack MVVM 精讲》和《Jetpack MVVM 最佳实践》的作者 KunMin...

  • MVVM实践

    最近的一两年,一直都在完成堆积的业务却忽略了架构。最近参加个新的项目,使用MVVM架构。 MVVM比MVC多了一个...

  • iOS MVVM架构总结

    参考:iOS 中MVC设计模式iOS MVVM架构iOS MVVM-框架介绍iOS 架构模式MVVM的实践总结iO...

  • 浅谈MVVM的好处

    最近一个月一直在寻找MVVM在我项目中的最佳实践 发现MVVM设计模式确实很棒,当然MVVM和MVP其实是差不多的...

  • MVVM

    问题:1、MVVM中的网络层在MVVM三个模块中的哪一块? 参考链接:MVVM最佳解决实践通过MVVM进行iOS开...

网友评论

  • 哈哈大笑呼呼呼呼:你好大神,能这个抽离成demo吗,感觉写的很好想学习下,谢谢
    哈哈大笑呼呼呼呼:@老章888 好的谢谢您
    勇往直前888:@SunMengK https://www.cnblogs.com/ludashi/p/4925042.html. 这篇是原创文章,我也是从这里学习的,demo也有。这才是真正的大神。

本文标题:MVVM和ReactiveObjC实践2018

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