MVVM架构浅析

简介: MVVM架构浅析

一.架构思想



  • 1.程序架构架构是为了便于程序员开发和维护代码。
  • 1.2.常见的架构思想
  • MVC:    M:模型 V:视图 C:控制器
  • MVVM:  M:模型 V:视图+控制器 VM:视图模型
  • MVCS:   M:模型 V:视图 C:控制器 C:服务类
  • VIPER    V:视图 I:交互器 P:展示器 E:实体 R:路由  VIPER构建iOS应用
  • 1.3.MVVM介绍
  • 模型(M):保存视图数据。
  • 视图+控制器(V):展示内容 + 如何展示
  • 视图模型(VM):处理展示的业务逻辑,包括按钮的点击,数据的请求和解析等等。


二.ReactiveCocoa + MVVM 实战一:登录界面



  • 2.1.需求:
  • 1.监听两个文本框的内容,有内容才允许按钮点击
  • 2.默认登录请求.
  • 2.2.用MVVM:实现,之前界面的所有业务逻辑分析:
  • 1.之前界面的所有业务逻辑都交给控制器做处理
  • 2.在MVVM架构中把控制器的业务全部搬去VM模型,也就是每个控制器对应一个VM模型.
  • 2.3.步骤:
  • 1.创建LoginViewModel类,处理登录界面业务逻辑.
  • 2.这个类里面应该保存着账号的信息,创建一个账号Account模型
  • 3.LoginViewModel应该保存着账号信息Account模型。
  • 4.需要时刻监听Account模型中的账号和密码的改变,怎么监听?
  • 5.在非RAC开发中,都是习惯赋值,在RAC开发中,需要改变开发思维,由赋值转变为绑定,可以在一开始初始化的时候,就给Account模型中的属性绑定,并不需要重写set方法。
  • 6.每次Account模型的值改变,就需要判断按钮能否点击,在VM模型中做处理,给外界提供一个能否点击按钮的信号.
  • 7.这个登录信号需要判断Account中账号和密码是否有值,用KVO监听这两个值的改变,把他们聚合成登录信号.
  • 8.监听按钮的点击,由VM处理,应该给VM声明一个RACCommand,专门处理登录业务逻辑.
  • 9.执行命令,把数据包装成信号传递出去
  • 10.监听命令中信号的数据传递
  • 11.监听命令的执行时刻
  • 2.4.控制器代码


@interface ViewController ()
@property (nonatomic, strong) LoginViewModel *loginViewModel;
@property (weak, nonatomic) IBOutlet UITextField *accountField;
@property (weak, nonatomic) IBOutlet UITextField *pwdField;
@property (weak, nonatomic) IBOutlet UIButton *loginBtn;
@end
- (LoginViewModel *)loginViewModel
{
    if (_loginViewModel == nil) {
       _loginViewModel = [[LoginViewModel alloc] init];
    }
   return _loginViewModel;
}
// 视图模型绑定
- (void)bindModel
{
    // 给模型的属性绑定信号
    // 只要账号文本框一改变,就会给account赋值
    RAC(self.loginViewModel.account, account) = _accountField.rac_textSignal;
    RAC(self.loginViewModel.account, pwd) = _pwdField.rac_textSignal;
    // 绑定登录按钮
    RAC(self.loginBtn,enabled) = self.loginViewModel.enableLoginSignal;
    // 监听登录按钮点击
   [[_loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    // 执行登录事件
    [self.loginViewModel.LoginCommand execute:nil];
  }];
}
  • 2.5.VM的代码
@interface LoginViewModel : NSObject
@property (nonatomic, strong) Account *account;
// 是否允许登录的信号
@property (nonatomic, strong, readonly) RACSignal *enableLoginSignal;
@property (nonatomic, strong, readonly) RACCommand *LoginCommand;
@end
@implementation LoginViewModel
- (Account *)account
{
      if (_account == nil) {
       _account = [[Account alloc] init];
     }
     return _account;
}
- (instancetype)init
{
       if (self = [super init]) {
          [self initialBind];
      }
      return self;
}
// 初始化绑定
- (void)initialBind
{
     // 监听账号的属性值改变,把他们聚合成一个信号。
     _enableLoginSignal = [RACSignal combineLatest:@[RACObserve(self.account, account),RACObserve(self.account, pwd)] reduce:^id(NSString *account,NSString *pwd){
     return @(account.length && pwd.length);
    }];
     // 处理登录业务逻辑
    _LoginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
  NSLog(@"点击了登录");
  return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
      // 模仿网络延迟
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
          [subscriber sendNext:@"登录成功"];
          // 数据传送完毕,必须调用完成,否则命令永远处于执行状态
          [subscriber sendCompleted];
      });
      return nil;
    }];
  }];
  // 监听登录产生的数据
  [_LoginCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
   if ([x isEqualToString:@"登录成功"]) {
      NSLog(@"登录成功");
   }
  }];
// 监听登录状态
[[_LoginCommand.executing skip:1] subscribeNext:^(id x) {
  if ([x isEqualToNumber:@(YES)]) {
      // 正在登录ing...
      // 用蒙版提示
      [MBProgressHUD showMessage:@"正在登录..."];
    }else
    {
         // 登录成功
         // 隐藏蒙版
         [MBProgressHUD hideHUD];
      }
   }];
 }


三.ReactiveCocoa + MVVM 实战二:网络请求数据


  • 3.1.在此提供以下豆瓣的免费API接口


3.1.1.百度搜索豆瓣进入官网


image.png

3.1.2.在首页滑到最下面点击在豆瓣工作

image.png

3.1.3.在豆瓣工作的首页滑动到最下面点击开发者


image.png


3.1.4.点击在你的应用里面使用API


image.png

3.1.5.点击豆瓣Api V2 (测试版)


image.png

3.1.6.API都在这里自己选吧

image.png


3.2.测试一下接口,在桌面生成一个plist文件,目的是方便查看打印的内容


image.png

3.3. 正是上代码,需求+分析+步骤


  • 3.3.1. 需求:请求豆瓣图书信息,url:https://api.douban.com/v2/book/search?q=基础
  • 3.3.2.分析:请求一样,交给VM模型管理
  • 3.3.3.步骤:
  • 1.控制器提供一个视图模型(requesViewModel),处理界面的业务逻辑
  • 2.VM提供一个命令,处理请求业务逻辑
  • 3.在创建命令的block中,会把请求包装成一个信号,等请求成功的时候,就会把数据传递出去。
  • 4.请求数据成功,应该把字典转换成模型,保存到视图模型中,控制器想用就直接从视图模型中获取。
  • 5.假设控制器想展示内容到tableView,直接让视图模型成为tableView的数据源,把所有的业务逻辑交给视图模型去做,这样控制器的代码就非常少了。


  • 3.3.4.效果如下


image.png


  • 3.3.5.控制器代码

#import "ViewController.h"
#import "JKGlobalHeader.h"
/**
 *  VM层
 */
#import "RequestViewModel.h"
#import "DataModel.h"
#define WIDTH   [UIScreen mainScreen].bounds.size.width
#define HEIGHT  [UIScreen mainScreen].bounds.size.height
@interface ViewController ()
@property(nonatomic,strong) RequestViewModel *requestViewModel;
@property (nonatomic, strong) UITableView *tableView;
@end
@implementation ViewController
 - (void)viewDidLoad {
      [super viewDidLoad];
     //导航栏的操作
    [self daohanglan];
// 创建tableView
self.tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, WIDTH, HEIGHT-64)];
self.tableView.dataSource = self.requestViewModel;
self.tableView.delegate = self.requestViewModel;
self.requestViewModel.tabview = self.tableView;
[self.view addSubview:self.tableView];
/**
 *  在这里也可以拿到数据
 */
   [self.requestViewModel.requestCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
    NSLog(@"%@",x);
}];
   [self.requestViewModel.requestCommand execute:@"开始"];
}
#pragma mark 导航栏
-(void)daohanglan{
   self.edgesForExtendedLayout = UIRectEdgeNone;
   self.title = @"MVVM的初次使用";
   self.navigationController.navigationBar.barTintColor = [UIColor brownColor];
   self.navigationController.navigationBar.titleTextAttributes = @{NSFontAttributeName:[UIFont systemFontOfSize:22],NSForegroundColorAttributeName:[UIColor whiteColor]};
}
//懒加载
-(RequestViewModel *)requestViewModel
{
   if (!_requestViewModel) {
     _requestViewModel = [[RequestViewModel alloc]init];
  }
  return _requestViewModel;
}
@end
  • 3.3.6.视图模型(VM)代码


#import <Foundation/Foundation.h>
 #import "JKGlobalHeader.h"
 @interface RequestViewModel : NSObject<UITableViewDelegate,UITableViewDataSource>
 /**
  *  请求命令
  */
 @property(nonatomic,strong,readonly) RACCommand *requestCommand;
 /**
  *  建立表格
  */
 @property(nonatomic,strong) UITableView *tabview;
 //模型数组
 @property (nonatomic, strong, readonly) NSArray *models;
 @end
• //  RequestViewModel.m
//  MVVM-网络请求
import "RequestViewModel.h"
import "AFNetworking.h"
import "DataModel.h"
import "MJExtension.h"
import "RequestTableViewCell.h"
import "UIImageView+WebCache.h"
@interface RequestViewModel ()
@end
@implementation RequestViewModel
-(instancetype)init
{
if (self = [super init]) {
[self setUP];
 }
return self;
}
-(void)setUP{
_requestCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {


/**
   *  把请求用一个信号包裹起来
   */
  RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
      // 1.创建请求管理者
      AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];
      //2.开始请求//count
      [mgr GET:@"https://api.douban.com/v2/book/search" parameters:@{@"q":@"美女"} success:^(AFHTTPRequestOperation * _Nonnull operation, id  _Nonnull responseObject) {
          NSLog(@"请求成功");
          NSArray *array =responseObject[@"books"];
          array = [DataModel mj_objectArrayWithKeyValuesArray:array];
          [subscriber sendNext:array];
      } failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
          NSLog(@"请求失败==%@",error);
      }];
      return nil;
  }];
  return signal;
  • }];
    // 获取请求的数据
    [_requestCommand.executionSignals.switchToLatest subscribeNext:^(NSArray *x) {
// 有了新数据,刷新表格
  _models = x;
  // 刷新表格
  [self.tabview reloadData];
 }];


RequestTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
  if (!cell) {
     cell = [[[NSBundle mainBundle]loadNibNamed:@"RequestTableViewCell" owner:self options:nil]lastObject];
     cell.selectionStyle = UITableViewCellSelectionStyleNone;
 }
  DataModel *model = self.models[indexPath.row];
  NSURL *url = [NSURL URLWithString:model.image];
  [cell.picture sd_setImageWithURL:url placeholderImage:nil];
  cell.bookName.text = model.title;
  return cell;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.models.count;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 139;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"iD";

@end


上面的额源码 密码: n9er


目录
相关文章
|
13天前
|
XML 前端开发 Android开发
Kotlin教程笔记(80) - MVVM架构设计
Kotlin教程笔记(80) - MVVM架构设计
|
15天前
|
前端开发 JavaScript 测试技术
android做中大型项目完美的架构模式是什么?是MVVM吗?如果不是,是什么?
在 Android 开发中,选择合适的架构模式对于构建中大型项目至关重要。常见的架构模式有 MVVM、MVP、MVI、Clean Architecture 和 Flux/Redux。每种模式都有其优缺点和适用场景,例如 MVVM 适用于复杂 UI 状态和频繁更新,而 Clean Architecture 适合大型项目和多平台开发。选择合适的架构应考虑项目需求、团队熟悉度和可维护性。
43 6
|
19天前
|
存储 Dart 前端开发
flutter鸿蒙版本mvvm架构思想原理
在Flutter中实现MVVM架构,旨在将UI与业务逻辑分离,提升代码可维护性和可读性。本文介绍了MVVM的整体架构,包括Model、View和ViewModel的职责,以及各文件的详细实现。通过`main.dart`、`CounterViewModel.dart`、`MyHomePage.dart`和`Model.dart`的具体代码,展示了如何使用Provider进行状态管理,实现数据绑定和响应式设计。MVVM架构的分离关注点、数据绑定和可维护性特点,使得开发更加高效和整洁。
146 3
|
25天前
|
存储 前端开发 测试技术
Android kotlin MVVM 架构简单示例入门
Android kotlin MVVM 架构简单示例入门
28 1
|
25天前
|
XML 前端开发 Android开发
Kotlin教程笔记(80) - MVVM架构设计
Kotlin教程笔记(80) - MVVM架构设计
25 1
|
1月前
|
XML 前端开发 Android开发
Kotlin教程笔记(80) - MVVM架构设计
本系列学习教程笔记详细讲解了Kotlin语法,适合需要深入了解Kotlin的开发者。对于希望快速学习Kotlin语法的读者,建议参考“简洁”系列教程。本文重点介绍了Kotlin实现MVVM架构的设计思路和代码实现,包括Model、ViewModel和View层的具体实现,以及如何通过LiveData和viewModelScope有效管理数据和内存,避免内存泄漏。此外,还讨论了MVVM架构的常见缺点及应对策略,帮助开发者在实际项目中更好地应用这一设计模式。
33 1
|
16天前
|
前端开发 Java 测试技术
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
23 0
|
1月前
|
存储 前端开发 Java
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
|
1月前
|
设计模式 前端开发 JavaScript
深入探索研究MVVM架构设计
【10月更文挑战第7天】
22 0
|
1月前
|
存储 前端开发 Java
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
52 0

热门文章

最新文章