既然是简单使用那就不讲那些概念了。什么是MVC、MVP、MVVM这些就忽略啦!很多大神写了很多,有关他们的优缺点都写的非常的好,我就没必要再写一遍了。
直接开始写怎么开始一个 MVVM+RAC 的教程了。
我喜欢在写代码的时候先写思路,这样就顺便把注释也写了。
写Demo和下厨是一样一样的,得有先材料。
那么写这个也要准备一下材料:
这里没有两个版本一个OC和一个Swift版本。如果你的项目是OC的你没必要在把Swift的下载下来,而且还要配置不然工程会报错。
Swift:
use_frameworks!
target 'Target名称' do
pod 'ReactiveCocoa', '5.0.0-alpha.3'
end
OC:
use_frameworks!
target 'Target名称' do
pod 'ReactiveObjC', '~> 2.1.0'
end
我用的是OC版本,网络我用到了AFNetworking
use_frameworks!
target 'Target名称' do
pod 'ReactiveObjC', '~> 2.1.0'
pod 'AFNetworking', '~> 3.0'
end
新建工程后:
#import
#import
因为是Demo所以就这么写了。
#import
@interface AccountModel : NSObject
@property(nonatomic,strong)NSString *userName;
@property(nonatomic,strong)NSString *pwd;
@end
#import
#import "AccountModel.h"
@interface LoginViewModel : NSObject
@property(nonatomic,strong)AccountModel *account;
@property(nonatomic,strong)RACSignal *listeningSignal;
@property(nonatomic,strong)RACSubject *btnColorSubject;
@property(nonatomic,strong)RACCommand *loginCommand;
@property(nonatomic,strong)UINavigationController *nav;
@end
#import "LoginViewModel.h"
#import "ListViewController.h"
@implementation LoginViewModel
-(AccountModel*)account{
if (!_account) {
_account = [[AccountModel alloc]init];
}
return _account;
}
-(instancetype)init{
if (self=[super init]) {
[self initalBind];
}
return self;
}
-(void)initalBind{
////创建信号 通过combinelatest 讲两个型号组合一起 通过一个宏RACObserve 来观察Account 里面的 属性的变化
_listeningSignal = [RACSignal combineLatest:@[RACObserve(self.account, userName),RACObserve(self.account, pwd)] reduce:^id(NSString *account,NSString *pwd){
////这里就判断 发送哪个信号出去 用来改变颜色
// if (account.length && pwd.length) {
// [self.btnColorSubject sendNext:@1];
// } else {
// [self.btnColorSubject sendNext:@2];
// }
return @(account.length&&pwd.length);
}];
////创建一个RACCommand 事件
// 1.signalBlock必须要返回一个信号,不能传nil.
// 2.如果不想要传递信号,直接创建空的信号[RACSignal empty];
// 3.RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。
// 4.RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。
_loginCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
RACSignal *loginSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
////这里就是模拟网络的一个block
[Netwrok getData:^(NSString *str) {
[subscriber sendNext:str];
[subscriber sendCompleted];
}] ;
return [RACDisposable disposableWithBlock:^{
NSLog(@"信号销毁");
}];
}];
return loginSignal;
}];
////这里就是订阅command 里面的信号 根据不同信号来跳转 和 处理业务
[_loginCommand.executionSignals.switchToLatest subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
if ([x isEqualToString:@"成功"]) {
ListViewController *vc = [[ListViewController alloc]init];
[self.nav pushViewController:vc animated:YES];
}
}];
////这里就是订阅 command 里面的 信号 使用skip 是过滤第一个信号因为默认会调用一次
[[_loginCommand.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
NSLog(@"%@",x);
if ([x isEqual:@(YES)]) {
NSLog(@"正在登录");
}else{
NSLog(@"%@",x);
}
}];
}
@end
#import "ViewController.h"
#import "LoginViewModel.h"
@interface ViewController ()
@property(nonatomic,strong)LoginViewModel *viewModel;
@property (weak, nonatomic) IBOutlet UITextField *nameTextField;
@property (weak, nonatomic) IBOutlet UITextField *pwdTextField;
@property (weak, nonatomic) IBOutlet UIButton *loginBtn;
@end
@implementation ViewController
-(LoginViewModel*)viewModel{
if (!_viewModel) {
_viewModel = [[LoginViewModel alloc]init];
}
return _viewModel;
}
- (void)viewDidLoad {
[super viewDidLoad];
////这里就是监听 每个控件值的变化
RAC(self.viewModel.account,userName) = self.nameTextField.rac_textSignal;
RAC(self.viewModel.account,pwd) = self.pwdTextField.rac_textSignal;
RAC(self.loginBtn,enabled) = self.viewModel.listeningSignal;
///这里是监听按钮的颜色 是有可以点击的时候改变颜色
///1. 这种方式是直接 监听按钮的一个enabled 属性,是否可以点击
[RACObserve(self.loginBtn, enabled) subscribeNext:^(id x) {
if ([x isEqual:@1]) {
self.loginBtn.backgroundColor = [UIColor greenColor];
} else {
self.loginBtn.backgroundColor = [UIColor redColor];
}
NSLog(@"是否可以点击%@",x);
}];
////2. 这种是再viewmodel 里面再创建一个RACSubject 信号 用来发送信号以便改变颜色
// self.viewModel.btnColorSubject = [RACSubject subject];
// [self.viewModel.btnColorSubject subscribeNext:^(id _Nullable x) {
// if ([x isEqual:@1]) {
// self.loginBtn.backgroundColor = [UIColor greenColor];
// } else {
// self.loginBtn.backgroundColor = [UIColor redColor];
// }
// NSLog(@"----%@",x);
// }];
/*这里就是 导航栏 放到ViewModel里面。也有人认为放到C里面去跳转。我个人比较倾向在ViewModel里面去跳转,既然ViewModel里面都是出来业务逻辑,那么跳转也应该属于其中一部分。*/
self.viewModel.nav = self.navigationController;
////监听按钮的点击事件,当按钮收到可以点击事件的时候 LoginCommand 就可以执行 execute 执行命名
[[_loginBtn rac_signalForControlEvents:1<<6] subscribeNext:^(__kindof UIControl * _Nullable x) {
[self.viewModel.loginCommand execute:nil];
}];
}
@end
#import
typedef void(^NetWorkBlock)(NSString *str);
typedef void(^NetWorkDataBlock)(id data);
@interface Netwrok : NSObject
+(void)getData:(NetWorkBlock)block;
+(void)getListData:(NetWorkDataBlock)block;
@end
#import "Netwrok.h"
@implementation Netwrok
+(void)getData:(NetWorkBlock)block{
block(@"成功");
}
+(void)getListData:(NetWorkDataBlock)block{
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
parameters[@"q"] = @"基础";
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"https://api.douban.com/v2/book/search" parameters:parameters progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
block(responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
}
@interface BookModel : NSObject
@property(nonatomic,strong)NSString *subtitle;
@property(nonatomic,strong)NSString *author;
@property(nonatomic,strong)NSString *pubdate;
@property(nonatomic,strong)NSString *image;
@property(nonatomic,strong)NSString *binding;
@property(nonatomic,strong)NSString *catalog;
+(BookModel*)bookWithDict:(NSDictionary*)dict;
@end
@implementation BookModel
+(BookModel*)bookWithDict:(NSDictionary *)dict{
BookModel *book = [[BookModel alloc]init];
book.subtitle = dict[@"subtitle"];
book.author = dict[@"author"];
book.pubdate = dict[@"pubdate"];
book.image = dict[@"image"];
book.catalog = dict[@"catalog"];
return book;
}
@end
#import "BookModel.h"
@interface BookTableViewCell : UITableViewCell
@property(nonatomic,strong)UILabel *titleLabel;
@property(nonatomic,strong)UILabel *dextLabel;
@property(nonatomic,strong)BookModel *book;
+(instancetype)cellWithTableView:(UITableView*)tableView;
@end
@implementation BookTableViewCell
+(instancetype)cellWithTableView:(UITableView*)tableView{
static NSString *identfier = @"BookTableViewCell";
//先在缓存池中取
BookTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identfier];
//缓存池中没有再创建,并添加标识,cell移出屏幕时放入缓存池以复用
if (cell == nil) {
cell = [[BookTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identfier];
}
return cell;
}
-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self=[super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.titleLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 200, 20)];
[self.contentView addSubview:self.titleLabel];
self.dextLabel= [[UILabel alloc]initWithFrame:CGRectMake(0, 20, 200, 20)];
[self.contentView addSubview:self.dextLabel];
}
return self;
}
-(void)setBook:(BookModel *)book{
_book = book;
self.titleLabel.text = book.subtitle;
self.dextLabel.text = book.pubdate;
}
@end
@interface ListViewModel : NSObject <UITableViewDataSource,UITableViewDelegate>
@property(nonatomic,strong)RACCommand *requeseCommand;
@property(nonatomic,strong)NSArray *models;
@property(nonatomic,strong)RACSubject *subject;
@property(nonatomic,strong)UINavigationController *nav;
@end
#import "ListViewModel.h"
#import "Network.h"
#import "BookModel.h"
#import "BookTableViewCell.h"
@implementation ListViewModel
-(instancetype)init{
if (self = [super init]) {
[self initabBind];
}
return self;
}
-(void)initabBind{
_requeseCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
[Network getList:^(id data) {
[subscriber sendNext:data];
[subscriber sendCompleted];
}];
return [RACDisposable disposableWithBlock:^{
NSLog(@"信号销毁");
}];
}];
return [requestSignal map:^id _Nullable(id _Nullable value) {
NSMutableArray *dictArray = value[@"books"];
NSArray *modelArray = [[dictArray.rac_sequence map:^id _Nullable(id _Nullable value) {
return [BookModel bookWithDict:value];
}]array];
return modelArray;
}];
}];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.models.count;
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
BookTableViewCell *cell = [BookTableViewCell cellWithTableView:tableView];
cell.book=self.models[indexPath.row];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
BookModel *book= self.models[indexPath.row];
UIViewController *vc = [[UIViewController alloc]init];
vc.title=book.subtitle;
vc.view.backgroundColor = [UIColor whiteColor];
[self.nav pushViewController:vc animated:YES];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 50;
}
@end
#import "ListViewController.h"
#import "ListViewModel.h"
#import "BookModel.h"
@interface ListViewController ()
@property(nonatomic,strong)UITableView *tableView;
@property(nonatomic,strong)ListViewModel *requestViewModel;
@end
@implementation ListViewController
-(ListViewModel*)requestViewModel{
if (!_requestViewModel) {
_requestViewModel = [[ListViewModel alloc]init];
}
return _requestViewModel;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor=[UIColor whiteColor];
self.tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
self.tableView.dataSource = self.requestViewModel;
self.tableView.delegate=self.requestViewModel;
self.requestViewModel.nav = self.navigationController;
[self.view addSubview:self.tableView];
RACSignal *requestSiganl = [self.requestViewModel.requeseCommand execute:nil];
[requestSiganl subscribeNext:^(id _Nullable x) {
self.requestViewModel.models = x;
[self.tableView reloadData];
}];
}
在此所有的代码都在这里了,写完你会发现结构确实要比MVC清晰多了。各项职能都很明确。
当然怎么去选择都是根据具体业务去实现,业务简单的完全就可以MVC了。没必要写成这样,复杂一点的,就可以使用MVVM让你的结构变得更加清晰。