iOS ReactiveCocoa MVVM

学习了在MVVM中如何使用RactiveCocoa,简单的写上一个demo。重点在于如何在MVVM各层之间使用RAC的信号来更方便的在各个层之间进行响应式数据交互。
demo需求:一个登录界面(登录界面只有账号和密码都有输入,登录按钮才可以点击操作),一个登录成功后跳转展示界面。
在这里插入图片描述
ViewModel中是负责登录的业务逻辑层,该层中负责数据验证,网络请求,数据存储等一些和ui无关的业务逻辑。
在这里插入图片描述
因为ViewModel层是独立于UI层而存在的,所以可以在没有UI的情况下我们就可以去实现相应模块的ViewModel层。这正好减少了个个层次间的耦合性,同时也提高了可测试性,总体上改善了可维护性。好废话少说,接下来要实现登录的Model层。

//
//  User.h
//  RACDemo
//
//  Created by johnny on 2024/6/5.
//#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface User : NSObject@property (nonatomic, copy) NSString *name;@property (nonatomic, copy) NSString *pwd;@endNS_ASSUME_NONNULL_END

接下来要实现登录的ViewModel层。

//
//  UserViewModel.h
//  RACDemo
//
//  Created by johnny on 2024/6/5.
//#import <Foundation/Foundation.h>
#import "ReactiveObjC.h"
#import "User.h"NS_ASSUME_NONNULL_BEGIN@interface UserViewModel : NSObject@property (nonatomic, strong) User *user;// 登录成功
@property (nonatomic, strong) RACSubject *successObject;// 认证失败
@property (nonatomic, strong) RACSubject *failObject;// 登录失败
@property (nonatomic, strong) RACSubject *errorObject;// 登录按钮是否可用
- (id)loginBtnIsValid;// 登录
-(void)login;@endNS_ASSUME_NONNULL_END

登录ViewModel层对应的类的头文件中内容如上所示。其实上面一些常用的信号可以抽象出来放到ViewModel的父类中。属性user是用来绑定用户输入的用户名和密码。上面三个定义信号successObject,failObject,errorObject用来发送网络请求的数据。

successObject 负责处理网络请求成功且符合正常业务逻辑事件。
failObject 负责处理网络请求成功不符合正常业务逻辑的处理
errorObject 负责处理网络异常处理。
结合项目中的实例来解释一下什么时候发送successObject信号,如何发送failureObject信号,何时使用errorObject信号。
以某些理财App中购买理财产品的业务流程为例。在用户下单之前先去判断用户是否实名认证以及绑定银行卡,如果用户已经实名和绑定银行卡就走正常支付流程(用户就是想去下单购买),VM就往VC发送successObject信号,当前VC就会根据信号的指示跳转到下单支付页面。 但是如果用户没有实名或者绑卡,那么VM就给VC发送failureObject信号,根据信号中的参数来判断是走实名认证流程还是走绑定银行卡流程。 errorObject就比较简单了,网络异常,后台服务器抛出的异常等不需要iOS这边做业务逻辑处理的,就放在errorObject中负责错误信息的展示。

文字说完了,如果有些小伙伴还不太明白,那看下面这张原理图吧。把三种信号我们可以类比成十字路口的红绿灯。successObject就是绿灯,可以走正常流程。failureObject是黄灯,先等一下,完成该做的就可以走绿灯了。而errorObject就是一红灯,报错异常,终止业务流程并提升错误信息。有图有真相,到这儿如果还不理解我就没招了。
在这里插入图片描述
备注:借用的图片
在Public方法中- (id) buttonIsValid; 负责返回登录按钮是否可用的信号。- (void)login;发起网络请求,调用登录网络接口。

//
//  UserViewModel.m
//  RACDemo
//
//  Created by johnny on 2024/6/5.
//#import "UserViewModel.h"@interface UserViewModel ()@property (nonatomic, strong)RACSignal *userNameSignal;
@property (nonatomic, strong)RACSignal *pswSignal;@property (nonatomic, strong) NSArray *requestData;@end@implementation UserViewModel-(User *)user {if (_user == nil) {_user = [[User alloc]init];}return _user;
}
// VCViewModel的初始化方法如下,负责初始化属性
-(instancetype)init {if (self= [super init]) {[self initialize];}return self;
}- (void)initialize {_userNameSignal = RACObserve(self, self.user.name);_pswSignal = RACObserve(self, self.user.pwd);// 初始化 success_successObject = [RACSubject subject];_failObject = [RACSubject subject];_errorObject = [RACSubject subject];
}// 合并两个输入框信号,并返回按钮bool类型的值
-(id)loginBtnIsValid {RACSignal *isValid = [RACSignal combineLatest:@[_userNameSignal, _pswSignal] reduce:^id (NSString *user, NSString *psw){return @(user.length > 3 && psw.length > 3);}];return isValid;
}// 模拟网络请求的发送,并发出网络请求成功的信号
-(void)login {// 网络请求_requestData = @[_user.name, _user.pwd];NSLog(@"网络请求");// 成功发送信号[_successObject sendNext:_requestData];[_failObject sendNext:@"认证失败"];[_errorObject sendNext:@"登录失败"];
}@end

上面是VM的实现,如果要进行单元测试的话,就对相应的VM类进行初始化,调用相应的函数进行单元测试即可。接着就是看如何在相应的VC模块中使用VM。

剩下的就是在VC中实例化响应的VM类。

//
//  LoginViewController.m
//  RACDemo
//
//  Created by johnny on 2024/6/5.
//#import "LoginViewController.h"
#import "ReactiveObjC.h"
#import "UserViewModel.h"@interface LoginViewController ()@property (nonatomic, strong) UITextField *accountField;
@property (nonatomic, strong) UITextField *pwdField;
@property (nonatomic, strong) UIButton *loginBtn;@property (nonatomic, strong) UserViewModel *viewModel;@end@implementation LoginViewController-(UserViewModel *)viewModel {if (_viewModel == nil) {_viewModel = [[UserViewModel alloc]init];}return _viewModel;
}- (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColor = [UIColor yellowColor];[self setUP];[self bindViewModel];//按钮点击事件@weakify(self);[[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {NSLog(@"点击登录按钮");@strongify(self);[self.viewModel login];}];//登录成功要处理的方法[_viewModel.successObject subscribeNext:^(id  _Nullable x) {NSLog(@"successObject %@", x);}];//fail[_viewModel.failObject subscribeNext:^(id  _Nullable x) {NSLog(@"failObject %@", x);}];//error[_viewModel.errorObject subscribeNext:^(id  _Nullable x) {NSLog(@"errorObject %@", x);}];
}//关联ViewModel
- (void)bindViewModel {RAC(self.viewModel.user, name) = _accountField.rac_textSignal;RAC(self.viewModel.user, pwd) = _pwdField.rac_textSignal;RAC(self.loginBtn, enabled) = _viewModel.loginBtnIsValid;[_viewModel.loginBtnIsValid subscribeNext:^(id  _Nullable x) {NSLog(@"loginBtnIsValid %@", x);self.loginBtn.backgroundColor = [UIColor systemPinkColor];}];
}- (void)setUP {self.accountField = [[UITextField alloc]initWithFrame:CGRectMake(100, 100, 200, 40)];self.accountField.backgroundColor = [UIColor redColor];[self.view addSubview:self.accountField];self.pwdField = [[UITextField alloc]initWithFrame:CGRectMake(100, 160, 200, 40)];self.pwdField.backgroundColor = [UIColor greenColor];[self.view addSubview:self.pwdField];self.loginBtn = [[UIButton alloc]initWithFrame:CGRectMake(100, 400, 50, 50)];self.loginBtn.backgroundColor = [UIColor blackColor];[self.view addSubview:self.loginBtn];
}@end

到此为止,一个完整模拟登录模块的RAC下的MVVM就实现完毕。

 platform :ios, '12.0'target 'RACDemo' do# Comment the next line if you don't want to use dynamic frameworksuse_frameworks!pod 'ReactiveObjC'pod 'ReactiveCocoa'endpost_install do |installer|installer.pods_project.targets.each do |target|target.build_configurations.each do |config|config.build_settings['ENABLE_BITCODE'] = 'NO'# Xcode 14 以后 bundle 需要需要签名config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0'
#      config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"endend
end

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/26240.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

白酒:茅台镇白酒的口感特点与品质保障

当我们谈及中国的白酒&#xff0c;茅台镇无疑是其中璀璨的一颗明珠。而在这片神奇的土地上&#xff0c;云仓酒庄凭借其与众不同的酿造工艺和卓着的品质保障&#xff0c;成为了茅台镇白酒的新一代佼佼者。今天&#xff0c;让我们一起领略云仓酒庄豪迈白酒的口感特点和品质魅力。…

win11 修改hosts提示无权限

win11下hosts的文件路径 C:\Windows\System32\drivers\etc>hosts修改文件后提示无权限。 我做了好几个尝试&#xff0c;都没个啥用~比如&#xff1a;右键 管理员身份运行&#xff0c;在其他版本的windows上可行&#xff0c;但是win11不行&#xff0c;我用的是微软账号登录的…

【Ardiuno】实验使用ESP32单片机实现高级web服务器暂时动态图表功能(图文)

接下来&#xff0c;我们继续实验示例代码中的Wifi“高级web服务器”&#xff0c;配置相关的无线密码后&#xff0c;开始实验 #include <WiFi.h> #include <WiFiClient.h> #include <WebServer.h> #include <ESPmDNS.h>const char *ssid "XIAOFE…

高通Android 12 右边导航栏改成底部显示

最近同事说需要修改右边导航栏到底部&#xff0c;问怎么搞&#xff1f;然后看下源码尝试下。 1、Android 12修改代码路径 frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java a/frameworks/base/services/core/java/com/android/server/wm/Display…

快速排序!

快速排序 算法思想partition函数函数签名变量定义选择基准元素初始化指针遍历并分区最终交换返回基准元素的位置完整函数 QuickSort函数函数定义终止条件划分(partition)递归调用 完整函数 算法思想 partition函数 partition函数是快速排序算法中的一个关键部分。它的作用是将…

测试bert_base不同并行方式下的推理性能

测试bert_base不同并行方式下的推理性能 一.测试数据二.测试步骤1.生成bert配置文件2.安装依赖3.deepspeed 4卡tp并行4.FSDP 4卡并行5.手动将权值平均拆到4张卡,单进程多卡推理6.手动切分成4份,基于NCCL实现pipeline并行 本文测试了bert_base模型在不同并行方式下的推理性能 约…

最短路径与最小生成树:Dijkstra、Prim与Kruskal算法详解

在图论中&#xff0c;最短路径和最小生成树问题是两个重要的课题。本文将介绍三种经典的算法&#xff1a;Dijkstra、Prim和Kruskal&#xff0c;并对它们进行对比分析。我们将讨论这些算法解决的问题、各自的优劣性以及它们之间的异同点&#xff0c;并提供完整的代码示例。 Dij…

为什么电容两端电压不能突变

我们先从RC延时电路说起吧&#xff0c;图1是最简单的RC延时电路&#xff0c;给一个阶跃的电压信号&#xff0c;电压会变成黄色曲线这个样子&#xff0c;这是为什么呢&#xff1f; 图1 电压跳变后&#xff0c;电源负极电子移动到电容下极板&#xff0c;排斥上极板电子流动到电源…

c++实现二叉搜索树(上)

宝贝们&#xff0c;好久不见&#xff0c;甚是想念&#x1f917;小吉断更了差多有10多天&#xff0c;在断更的日子里&#xff0c;小吉也有在好好学习数据结构与算法&#xff0c;但是学的并不多而且学的并不是很认真。主要是中途笔记本屏出现问题了&#xff08;这件事有点让小吉我…

Neo4j连接

终端输入&#xff1a; neo4j console 浏览器访问&#xff1a;http://localhost:7474/ 输入用户名和密码&#xff1a;neo4j&#xff0c; 梦想密码&#xff08;首次neo4j&#xff09; 代码连接用新的服务器地址&#xff1a; g Graph(neo4j://localhost:7687, auth(neo4j, ))…

函数递归(C语言)(详细过程!)

函数递归 一. 递归是什么1.1 递归的思想1.2 递归的限制条件 二. 递归举例2.1 求n的阶乘2.2 按顺序打印一个整数的每一位 三. 递归与迭代3.1 求第n个斐波那契数 一. 递归是什么 递归是学习C语言很重要的一个知识&#xff0c;递归就是函数自己调用自己&#xff0c;是一种解决问题…

2024年中国移动游戏市场研究报告

来源&#xff1a;点点数据&#xff1a; 近期历史回顾&#xff1a; 面向水泥行业的5G虚拟专网技术要求&#xff08;2024&#xff09;.pdf 2024年F5G-A绿色万兆全光园区白皮书.pdf 2024年全球废物管理展望报告.pdf 内容管理系统 2024-2025中国羊奶粉市场消费趋势洞察报告.pdf 20…

Web端在线Stomp服务测试与WebSocket服务测试

Stomp服务测试 支持连接、发送、订阅、接收&#xff0c;可设置请求头、自动重连 低配置云服务器&#xff0c;首次加载速度较慢&#xff0c;请耐心等候 预览页面&#xff1a;http://www.daelui.com/#/tigerlair/saas/preview/lxbho9lkzvgc 演练页面&#xff1a;http://www.da…

Django之云存储(一)

一、介绍 用户上传的文件以及项目中使用的静态文件,除了保存在本地服务器,还在可以保存在云服务中,比如: 阿里云七牛云(课程选用)亚马逊云等1.1、使用方式 注册账号 七牛云开发者平台 实名认证 创建空间

pycharm终端pip安装模块成功但还是显示找不到 ModuleNotFoundError: No module named

报错信息&#xff1a; ModuleNotFoundError: No module named 但是分明已经安装过此模块&#xff1a; 在cmd运行pip list 查看所有安装过的包找到了安装过&#xff1a; 如果重新安装就是这样&#xff1a;显示已经存在了 问题排查&#xff1a; 直接根据重新安装的显示已存在的…

Beego 使用教程 9:ORM 操作数据库(上)

beego 是一个用于Go编程语言的开源、高性能的 web 框架 beego 被用于在Go语言中企业应用程序的快速开发&#xff0c;包括RESTful API、web应用程序和后端服务。它的灵感来源于Tornado&#xff0c; Sinatra 和 Flask beego 官网&#xff1a;http://beego.gocn.vip/ 上面的 be…

HCIP认证笔记(判断题)

192、两台LSR之间交换hello消息&#xff0c;触发LDP会话建立&#xff0c;hello消息会携带传输地址&#xff0c;传输地址大的一方将作为主动方&#xff1b; 193、两台LSR发送hello消息&#xff0c;其中hello消息会采用组播或单播的形式发送&#xff1b; 194、LSR ID&#xff1a;…

pikachu靶场通关全流程

目录 暴力破解&#xff1a; 1.基于表单的暴力破解&#xff1a; 2.验证码绕过(on server)&#xff1a; 3.验证码绕过(on client)&#xff1a; token防爆破&#xff1a; XSS&#xff1a; 1.反射型xss(get)&#xff1a; 2.反射性xss(post)&#xff1a; 3.存储型xss&#…

2024中国网络安全产品用户调查报告(发布版)

自2020年始&#xff0c;人类进入了21世纪的第二个十年&#xff0c;全球进入了百年未有之大变局&#xff0c;新十年的开始即被新冠疫情逆转了全球化发展的历程&#xff0c;而至2022年3月俄乌战争又突然爆发&#xff0c;紧接着2023年7月“巴以冲突"皱起&#xff0c;世界快速…

7.Nginx动静分离

介绍 把动态和静态请求分开,不能理解成只是单纯的把动态页面和静态页面物理分离。 动静分离从目前实现角度分为两种: 1.纯粹把静态文件独立成单独的域名,放在独立的静态资源服务器上,目前主流推崇的方案。 2.动态和静态文件混合在一起发布,通过nginx来分开。 通过loc…