iOS——教你如何使用ReactiveCocoa和MVVM为代码解耦构建清爽APP
来源:互联网 发布:满档红帽哥布林数据 编辑:程序博客网 时间:2024/06/06 00:00
欢迎进入我的个人域名博客:http://zhoulingyu.com
1. MVVM简介
不过多赘述MVC,用最通俗的方式解说MVVM。
- 拆解:
- M:
Model
,包括数据模型、访问数据库的操作和网络请求等 - V:
View
,包括了iOS中的View
和controller
组成,负责 UI 的展示,绑定 viewModel 中的属性 - VM:
ViewModel
,负责从Model
中获取View
所需的数据,转换成View
可以展示的数据,并暴露公开的属性和命令供View
进行绑定 - Binder:这是我最近发现的,在标准MVVM中没有提到的一部分,但是如果使用MVVM + ReactiveCocoa就会自然地写出这一层。这一层主要为了实现响应式编程的功能,实现
View
和ViewModel
的同步
- M:
MVC——>MVVM:
MVC是苹果官方推荐的开发模式,但是伴随这这模式产生的问题非常的多,这是随着项目的逐渐扩大、架构的逐渐复杂显示出来的,这也是为什么MVC也被调侃成Massive View Controller(重量级视图控制器)。大多数情况下,小型项目MVC开发不会带来太大的负担,即使你将大量的逻辑代码(不包括通用的工具类逻辑)放在了ViewController中,但只要该部分由一个人维护,相对来说还是可以保持逻辑清晰的。
但当项目越来越大时,或者一个模块会有多个人维护时,读代码变成了一件非常困难的事,并且,MVC模式的iOS开发一直存在难以测试的问题。博主在做JAVA开发时JUnit的测试就像每天的必修课一样。开始iOS开发后,加上第二家公司一直没有QA,线上发现的BUG简直就是每天的噩梦。MVVM带来的好处是
VM
层可以方便的做测试,因为VM
层是独立的逻辑,脱离对View
和Model
的依赖。少写字,多写代码,赶紧进入下一部分介绍,尽快去了解如何编码。
2. ReactiveCocoa
2.1 简介
简介: ReactiveCocoa简称RAC,集合了函数式编程和响应式编程,这也是为什么ReactiveCocoa被描述为函数响应式编程(FRP)框架。
ReactiveCocoa解决的问题:iOS开发中有多种事件处理方式,相信你一定也曾想过这些坑爹的地方,通常有这些事件处理方式:Action、delegate、Notification、KVO。并且通常这些代码总是散落在代码的各个角落,几度分散。ReactiveCocoa为事件提供了很多处理方法,可以把要处理的事情,和监听的事情的代码放在一起,非常方便管理。
- 关于ReactiveCocoa的基本用法,希望你能认真的阅读这篇博文
2.2 常用宏
RAC(TARGET, [KEYPATH, [NIL_VALUE]]):
用于给某个对象的某个属性绑定。
// 文本框文字改变时修改label的文字 RAC(self.labelView,text) = _textField.rac_textSignal;
**RACObserve(self, name):**
监听某个对象的某个属性,返回的是信号,可以用来代替KVO
[RACObserve(self.view, center) subscribeNext:^(id x) { NSLog(@"%@",x);}];
3. 实践
3.1 实现内容
做一个简单的登陆功能,两个输入框,一个登陆按钮。
简单的用户名密码验证,要求都在6位数以上即可,不符合要求时禁用登陆按钮。
3.2 Coding
界面大概是这样的感觉,简单的拉一个即可:
M层
抽出简单的User模型,Thin Model,不包含功能型方法:
#import <Foundation/Foundation.h>@interface User : NSObject@property (nonatomic, copy) NSString *username;@property (nonatomic, copy) NSString *password;@end
VM层
#import <Foundation/Foundation.h>#import "ReactiveCocoa.h"@interface LoginViewModel : NSObject@property (nonatomic, strong) NSString *userName;@property (nonatomic, strong) NSString *password;// 成功信号@property (nonatomic, strong) RACSubject *successSubject;// 失败信号@property (nonatomic, strong) RACSubject *failureSubject;// 错误信号@property (nonatomic, strong) RACSubject *errorSubject;/** * 按钮是否可点信号 * * @return */- (RACSignal *)validSignal;/** * 登陆操作 */- (void)login;@end
#import "LoginViewModel.h"#import "User.h"@interface LoginViewModel ()/** 用户名改变信号 */@property (nonatomic, strong) RACSignal *userNameSignal;/** 密码改变信号 */@property (nonatomic, strong) RACSignal *passwordSignal;/** 请求数据(模拟) */@property (nonatomic, strong) NSArray *requestData;@end@implementation LoginViewModel- (instancetype)init { if (self = [super init]) { // RACObserve(self, name):监听某个对象的某个属性,返回的是信号。 _userNameSignal = RACObserve(self, userName); _passwordSignal = RACObserve(self, password); _successSubject = [RACSubject subject]; _failureSubject = [RACSubject subject]; _errorSubject = [RACSubject subject]; } return self;}/** * 按钮是否可点信息 * * @return */- (RACSignal *)validSignal { RACSignal *validSignal = [RACSignal combineLatest:@[_userNameSignal, _passwordSignal] reduce:^id(NSString *userName, NSString *password){ // 要求用户名和密码大于6位数 return @(userName.length >= 6 && password.length >= 6); }]; return validSignal;}/** * 登陆操作 */- (void)login{ // 网络请求进行登录,当然这里只是模拟一下 User *user = [[User alloc] init]; user.username = self.userName; user.password = self.password; _requestData = @[user]; // 成功发送成功的信号 [_successSubject sendNext:_requestData]; // 如果失败发送失败的信息号}@end
通过这一层,你是否发现,VM是可以单独测试的,也是可以单独编写的,即使你没有写 View
层,你可以编写针对VM的Unit进行功能测试,确保VM无误后继续编写后续代码。
V层
首先我们要通过RAC实现一部分UI的功能——输入文字的时候同步将文字保存起来&&控制按钮的禁用状态。
想想看我们通常是怎么做的?
- 通过实现UITextField的代理
- 在
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;
方法中获取输入的文字,赋值给username属性和password属性 - 再判断username和password是否符合要求
- 再设置按钮的enabled属性
是不是看一看就觉得乱糟糟的,按钮的addTarget在一个地方,代理又在一个地方,再加上判断用户名密码合法逻辑单独抽出的方法。OMG。
ReactiveCocoa是怎么做的?
RAC(self.viewModel, userName) = self.tfUserName.rac_textSignal;RAC(self.viewModel, password) = self.tfPassword.rac_textSignal;RAC(self.btLogin, enabled) = [self.viewModel validSignal];
三句话,清清爽爽。
再加上 ViewModel
的信号绑定,将上面的代码放到一个方法中,命名为bindModel
最后的代码如下:
#import "ViewController.h"#import "ReactiveCocoa.h"#import "LoginViewModel.h"#import "User.h"@interface ViewController ()@property (nonatomic, strong) LoginViewModel *viewModel;@property (weak, nonatomic ) IBOutlet UITextField *tfUserName;@property (weak, nonatomic ) IBOutlet UITextField *tfPassword;@property (weak, nonatomic ) IBOutlet UIButton *btLogin;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; [self bindModel];}/** * 绑定Model中的各种事件 */- (void)bindModel { self.viewModel = [[LoginViewModel alloc] init]; RAC(self.viewModel, userName) = self.tfUserName.rac_textSignal; RAC(self.viewModel, password) = self.tfPassword.rac_textSignal; RAC(self.btLogin, enabled) = [self.viewModel validSignal];// @weakify(self); // 订阅登录成功信号并作出处理 [self.viewModel.successSubject subscribeNext:^(NSArray * x) {// @strongify(self); User *user = x[0]; NSLog(@"username:%@\tpassword:%@", user.username, user.password); NSLog(@"登陆成功"); }]; // 订阅登录失败信号并作出处理 [self.viewModel.failureSubject subscribeNext:^(id x) { NSLog(@"登陆失败"); }]; // 订阅登录错误信号并作出处理 [self.viewModel.errorSubject subscribeNext:^(id x) { NSLog(@"登陆错误"); }]; // 添加按钮点击事件 [[self.btLogin rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) { [self.viewModel login]; }];}@end
这样,ViewController中事件处理的所有代码被集中在一起,方便管理,你的代码变得如此清爽、低耦合。
代码
以上代码地址请点这里。
补充
小鱼最近在换工作哟~各路的朋友有推荐的请务必介绍我哟~
简历在这里
有什么问题都可以在博文后面留言,或者微博上私信我。
博主主要写javaee和iOS的。
希望大家一起进步。
CSDN: CSDN博客地址
我的微博:小鱼周凌宇
- iOS——教你如何使用ReactiveCocoa和MVVM为代码解耦构建清爽APP
- IOS ReactiveCocoa 和 MVVM 入门
- iOS之ReactiveCocoa简介和使用(二):MVVM
- ios MVVM与reactivecocoa
- 使用ReactiveCocoa初探MVVM
- ReactiveCocoa 和 MVVM 入门
- ReactiveCocoa 和 MVVM 入门
- ReactiveCocoa 和 MVVM 入门
- ReactiveCocoa 和 MVVM 入门
- ReactiveCocoa 和 MVVM 入门
- ReactiveCocoa和MVVM
- ReactiveCocoa 和 MVVM 入门
- ReactiveCocoa 和 MVVM 入门
- ReactiveCocoa 和 MVVM 入门
- ReactiveCocoa 和 MVVM 入门
- ReactiveCocoa 和 MVVM 入门
- ReactiveCocoa 和 MVVM 入门
- 关于 MVVM 和 ReactiveCocoa
- 二叉排序数的基本操作(构造、插入、删除)
- PHP代码调试
- linux 下基本的I/O系统函数
- NYOJ 812 水题 十进制 二进制 转换
- hdu 3631 Shortest Path floyd 解题报告
- iOS——教你如何使用ReactiveCocoa和MVVM为代码解耦构建清爽APP
- Ubuntu 删除个人配置文件
- 匹配汉字的正则表达式
- win2008R2域环境配置用户主目录
- 漫谈C++:良好的编程习惯与编程要点
- oralce_1 SQL 基础
- Leetcode 100. Same Tree
- 约瑟夫问题
- RDD生成全生命周期彻底研究和思考(第八篇)