作品链接:http://www.jianshu.com/users/1e0f5e6f73f6/top_articles
1.介绍MVVM架构思想
1 程序为什么要架构:便于程序员开发和维护代码。
2 常见的架构思想:
MVC
M:模型 V:视图 C:控制器MVVM
M:模型 V:视图+控制器 VM:视图模型MVCS
M:模型 V:视图 C:控制器 C:服务类VIPER
V:视图 I:交互器 P:展示器 E:实体 R:路由 (http://www.cocoachina.com/ios/20140703/9016.html)
3 MVVM介绍
模型
(M):保存视图数据。视图+控制器
(V):展示内容 + 如何展示视图模型
(VM):处理展示的业务逻辑,包括按钮的点击,数据的请求和解析等等。
2.ReactiveCocoa + MVVM 登录界面
1.需求
- 1.监听两个文本框的内容,有内容才允许按钮点击
- 2.默认登录请求.
2.分析
- 1.界面的所有业务逻辑都交给控制器做处理
- 2.在MVVM架构中把控制器的业务全部搬去VM模型,也就是每个控制器对应一个VM模型.
3.步骤
1.创建LoginViewModel类,处理登录界面业务逻辑.
2.这个类里面应该保存着账号的信息,创建一个账号Account模型
3.PHLoginViewModel应该保存着账号信息Account模型。
4.需要时刻监听Account模型中的账号和密码的改变,怎么监听?
5.在非RAC开发中,都是习惯赋值,在RAC开发中,需要改变开发思维,由赋值转变为绑定,可以在一开始初始化的时候,就给Account模型中的属性绑定,并不需要重写set方法。
6.每次Account模型的值改变,就需要判断按钮能否点击,在VM模型中做处理,给外界提供一个能否点击按钮的信号.
7.这个登录信号需要判断Account中账号和密码是否有值,用KVO监听这两个值的改变,把他们聚合成登录信号.
8.监听按钮的点击,由VM处理,应该给VM声明一个RACCommand,专门处理登录业务逻辑.
9.执行命令,把数据包装成信号传递出去
10.监听命令中信号的数据传递
11.监听命令的执行时刻
4.代码实现
4.1 控制器的代码
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *accountField;
@property (weak, nonatomic) IBOutlet UITextField *pwdField;
@property (weak, nonatomic) IBOutlet UIButton *loginBtn;
@property (nonatomic, strong) PHLoginViewModel *phLoginVM;
@end
@implementation ViewController
- (PHLoginViewModel *)phLoginVM
{
if (_phLoginVM == nil) {
_phLoginVM = [[PHLoginViewModel alloc] init];
}
return _phLoginVM;
}
// MVVM:
// VM:视图模型,处理界面上所有业务逻辑
// 每一个控制器对应一个VM模型
// VM:最好不要包括视图V
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self phBindViewModel];
[self phLoginEvent];
}
// 绑定viewModel
- (void)phBindViewModel
{
// 1.给视图模型的账号和密码绑定信号
RAC(self.phLoginVM, account) = _accountField.rac_textSignal;
RAC(self.phLoginVM, pwd) = _pwdField.rac_textSignal;
}
// 登录事件
- (void)phLoginEvent
{
// 1.处理文本框业务逻辑
// 设置按钮能否点击
RAC(_loginBtn, enabled) = self.phLoginVM.phLoginEnableSiganl;
// 2.监听登录按钮点击
[[_loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
// 处理登录事件
[self.phLoginVM.phLoginCommand execute:nil];
}];
}
4.2 VM的代码
- VM.h的代码
// 保存登录界面的账号和密码
@property (nonatomic, strong) NSString *account;
@property (nonatomic, strong) NSString *pwd;
// 处理登录按钮是否允许点击
@property (nonatomic, strong, readonly) RACSignal *phLoginEnableSiganl;
///** 登录按钮命令 */
@property (nonatomic, strong, readonly) RACCommand *phLoginCommand;
- VM.m的代码
- (instancetype)init
{
if (self = [super init]) {
[self setup];
}
return self;
}
// 初始化操作
- (void)setup
{
//1.处理登录点击的信号
_phLoginEnableSiganl = [RACSignal combineLatest:@[RACObserve(self, account),RACObserve(self, pwd)] reduce:^id(NSString *account, NSString *pwd){
return @(account.length && pwd.length);
}];
//2.处理登录点击命令
_phLoginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
// block:执行命令就会调用
// block作用:事件处理
// 发送登录请求
NSLog(@"发送登录请求");
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 发送数据
[subscriber sendNext:@"请求登录数据"];
[subscriber sendCompleted];
});
return nil;
}];
}];
//3.处理登录请求返回的结果
[_phLoginCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
//4.处理登录执行过程
[[_phLoginCommand.executing skip:1] subscribeNext:^(id x) {
if ([x boolValue] == YES) {
// 正在执行
NSLog(@"正在执行");
// 显示蒙版
[MBProgressHUD showMessage:@"正在登录ing......"];
} else {
// 执行完成
// 隐藏蒙版
[MBProgressHUD hideHUD];
NSLog(@"执行完成");
}
}];
}
3.ReactiveCocoa + MVVM 网络请求数据
1.需求
- 请求豆瓣音乐信息,url:https://api.douban.com/v2/book/search?q=经典
2.分析
- 请求一样,交给VM模型管理
3.步骤
1.控制器提供一个视图模型(PHRequesViewModel),处理界面的业务逻辑
2.VM提供一个命令,处理请求业务逻辑
3.在创建命令的block中,会把请求包装成一个信号,等请求成功的时候,就会把数据传递出去。
4.请求数据成功,应该把字典转换成模型,保存到视图模型中,控制器想用就直接从视图模型中获取。
5.假设控制器想展示内容到tableView,直接让视图模型成为tableView的数据源,
把所有的业务逻辑交给视图模型去做,这样控制器的代码就非常少了。
4.控制器的代码
@interface ViewController ()
/** 请求视图模型 */
@property (nonatomic, strong) PHRequestViewModel *phRequestVM;
@end
@implementation ViewController
- (PHRequestViewModel *)phRequestVM
{
if (_phRequestVM == nil) {
_phRequestVM = [[PHRequestViewModel alloc] init];
}
return _phRequestVM;
}
- (void)viewDidLoad {
[super viewDidLoad];
// 创建tableView
UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
tableView.dataSource = self.phRequestVM;
self.phRequestVM.tableView = tableView;
[self.view addSubview:tableView];
// 执行请求
[self.phRequestVM.phRequestCommand execute:nil];
}
5.VM的代码
@interface PHRequestViewModel : NSObject<UITableViewDataSource>
// 请求命令
@property (nonatomic, strong, readonly) RACCommand *phRequestCommand;
// 模型数组
@property (nonatomic, strong, readonly) NSArray *models;
// 控制器中的view
@property (nonatomic, weak) UITableView *tableView;
@end
@implementation PHRequestViewModel
- (instancetype)init
{
if (self = [super init]) {
[self setup];
}
return self;
}
- (void)setup
{
_phRequestCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
// 执行命令
// 发送请求
// 创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 创建请求管理者
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:@"https://api.douban.com/v2/music/search" parameters:@{@"q":@"经典"} success:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
// 请求成功的时候调用
NSLog(@"%@",responseObject);
// 输出plist文件
[responseObject writeToFile:@"/Users/apple/Desktop/yinyue.plist" atomically:YES];
// 请求成功的时候调用
[subscriber sendNext:responseObject];
[subscriber sendCompleted];
} failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
}];
return nil;
}];
// 在返回数据信号时,把数据中的字典映射成模型信号,传递出去
return [signal map:^id(NSDictionary *value) {
NSMutableDictionary *dictArr = value[@"musics"];
// 字典转模型,遍历字典中的所有元素,全部映射成模型,并且生成数组
NSArray *modelArr = [[dictArr.rac_sequence map:^id(id value) {
return [PHMusic ph_MusicWithDict:value];
}] array];
return modelArr;
}];
}];
// 获取请求的数据
[_phRequestCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
// 有了新数据
_models = x;
// 刷新表格
[self.tableView reloadData];
}];
}
# pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.models.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
PHMusic *music = self.models[indexPath.row];
cell.textLabel.text = music.title;
cell.imageView.image = [UIImage imageNamed:music.image];
return cell;
}