天天看點

MVVM + RAC 實踐

MVP

mvp的全稱為Model-View-Presenter,Model提供資料,View負責顯示,Controller/Presenter負責邏輯的處理。MVP與MVC有着一個重大的差別:在MVP中View并不直接使用Model,它們之間的通信是通過Presenter (MVC中的Controller)來進行的,所有的互動都發生在Presenter内部,而在MVC中View會直接從Model中讀取資料而不是通過 Controller。
MVVM + RAC 實踐

資料關系

  • View 接收使用者互動請求
  • View 将請求轉交給 Presenter
  • Presenter 操作Model進行資料更新
  • Model 通知Presenter資料發生變化
  • Presenter 更新View資料

方式

  • 各部分之間的通信,都是雙向的。
  • View 與 Model 不發生聯系,都通過 Presenter 傳遞。
  • View 非常薄,不部署任何業務邏輯,稱為"被動視圖"(Passive View),即沒有任何主動性,而 Presenter非常厚,所有邏輯都部署在那裡。
  • Presenter 可以另一種了解為View和Model的管家

優勢

  1. Model與View完全分離,修改互不影響
  2. 更高效地使用,因為所有的邏輯互動都發生在一個地方—Presenter内部
  3. 一個Preseter可用于多個View,而不需要改變Presenter的邏輯(因為View的變化總是比Model的變化頻繁)。
  4. 更便于測試。把邏輯放在Presenter中,就可以脫離使用者接口來測試邏輯(單元測試)

我們來看下MVP模式下的三個特性的分析:

  • 任務均攤--我們将最主要的任務劃分到Presenter和Model,而View的功能較少(雖然上述例子中Model的任務也并不多)。
  • 可測試性--非常好,由于一個功能簡單的View層,是以測試大多數業務邏輯也變得簡單
  • 易用性--在我們上邊不切實際的簡單的例子中,代碼量是MVC模式的2倍,但同時MVP的概念卻非常清晰

“iOS 中的MVP意味着可測試性強、代碼量大。”

MVP代碼範例

MVPViewController.m

#import "MVPViewController.h"
#import "MVPPresenter.h"
#import "MVPCell.h"
#import "MVPModel.h"

@implementation MVPViewController
- (void)viewDidLoad {
   [super viewDidLoad];
   // Do any additional setup after loading the view.
   self.view.backgroundColor = [UIColor whiteColor];

   MVPPresenter *present = [MVPPresenter new];
   MVPCell *view = [MVPCell new];
   MVPModel *model = [MVPModel new];

   //擷取資料
   model.name = @"name1";

   //視圖布局
   [self.view addSubview:view];

   //交給presenter處理 ,避免 view和 model 之間的互動
   [present setPreModel:model];
   [present setPreView:view];
}

           

MVPPresenter .h /.m

#import <Foundation/Foundation.h>
#import "MVPModel.h"
#import "MVPCell.h"
@interface MVPPresenter : NSObject 
@property(nonatomic,strong)MVPCell *MVPView;
@property(nonatomic,strong)MVPModel *model;

-(void)setPreView:(MVPCell *)view;
-(void)setPreModel:(MVPModel *)model;

-(void)clickChangName;
           
#import "MVPPresenter.h"
@implementation MVPPresenter
- (instancetype)init
{
    self = [super init];
    if (self) {

    }
    return self;
}

-(void)setPreModel:(MVPModel *)model{
    self.model = model;
}

-(void)setPreView:(MVPCell *)view{
    self.MVPView = view;
    [self.MVPView setlabel:_model.name];
}

-(void)clickChangName{

    NSLog(@"name change %d",arc4random()%);

}
           

MVVM

MVVM是Model-View-ViewModel的簡寫。微軟的WPF帶來了新的技術體驗,如Silverlight、音頻、視訊、3D、動畫……,這導緻了軟體UI層更加細節化、可定制化。同時,在技術層面,WPF也帶來了 諸如Binding、Dependency Property、Routed Events、Command、DataTemplate、ControlTemplate等新特性。MVVM(Model-View-ViewModel)架構的由來便是MVP(Model-View-Presenter)模式與WPF結合的應用方式時發展演變過來的一種新型架構架構。它立足于原有MVP架構并且把WPF的新特性糅合進去,以應對客戶日益複雜的需求變化。
MVVM + RAC 實踐

它和MVP模式看起來非常像:

MVVM将ViewController視作View 在View和Model之間沒有緊密的聯系 此外,它還有像監管版本的MVP那樣的綁定功能,但這個綁定不是在View和Model之間而是在View和ViewModel之間。

那麼問題來了,在iOS中ViewModel實際上代表什麼?

它基本上就是UIKit下的每個控件以及控件的狀态。ViewModel調用會改變Model同時會将Model的改變更新到自身并且因為我們綁定了View和ViewModel,第一步就是相應的更新狀态。

資料關系

  • View 接收使用者互動請求
  • View 将請求轉交給ViewModel
  • ViewModel 操作Model資料更新
  • Model 更新完資料,通知ViewModel資料發生變化
  • ViewModel 更新View資料

方式

雙向綁定。View/Model的變動,自動反映在 ViewModel,反之亦然。

MVVM 模式将 Presenter 改名為 ViewModel,基本上與 MVP 模式完全一緻。

唯一的差別是,它采用雙向綁定(data-binding):View <->ViewModel , ViewModel作為Model中值得的映射,是資料發生改變時,通知View中發生改變 ,以後不需要考慮View和Model 之間的互動更新,隻需着手界面布局邏輯即可

綁定

如果我們自己不想自己實作,那麼我們有兩種選擇:

  • 基于KVO的綁定庫如 RZDataBinding 和 SwiftBond
  • 完全的函數響應式程式設計,比如像ReactiveCocoa、RxSwift或者 PromiseKit

事實上,尤其是最近,你聽到MVVM就會想到ReactiveCoca,反之亦然。盡管通過簡單的綁定來使用MVVM是可實作的,但是ReactiveCocoa卻能更好的發揮MVVM的特點。

但是關于這個架構有一個不得不說的事實:強大的能力來自于巨大的責任。當你開始使用Reactive的時候有很大的可能就會把事情搞砸。換句話來說就是,如果發現了一些錯誤,調試出這個bug可能會花費大量的時間,看下函數調用棧:

MVVM + RAC 實踐

使用

  • 将viewModel 中nameStr與Model 中name相對應;
  • View中label的text值将與nameStr進行綁定(KVO鍵值觀察)
  • 這樣model的值發生改變時 ,View會自動發生改變
  • View 和Model通過ViewModel實作動态關聯

MVVM優點

MVVM模式和MVC模式一樣,主要目的是分離視圖(View)和模型(Model),有幾大優點:

  1. 低耦合。View可以獨立于Model變化和修改,一個ViewModel可以綁定到不同的”View”上,當View變化的時候Model可以不變,當Model變化的時候View也可以不變。
  2. 可重用性。你可以把一些視圖邏輯放在一個ViewModel裡面,讓很多view重用這段視圖邏輯。
  3. 獨立開發。開發人員可以專注于業務邏輯和資料的開發(ViewModel),設計人員可以專注于頁面設計,生成xml代碼。
  4. 可測試。界面素來是比較難于測試的,而現在測試可以針對ViewModel來寫。

讓我們再來看看關于三個特性的評估:

  • 任務均攤 -- 在例子中并不是很清晰,但是事實上,MVVM的View要比MVP中的View承擔的責任多。因為前者通過ViewModel的設定綁定來更新狀态,而後者隻監聽Presenter的事件但并不會對自己有什麼更新。
  • 可測試性 -- ViewModel不知道關于View的任何事情,這允許我們可以輕易的測試ViewModel。同時View也可以被測試,但是由于屬于UIKit的範疇,對他們的測試通常會被忽略。
  • 易用性 -- 在我們例子中的代碼量和MVP的差不多,但是在實際開發中,我們必須把View中的事件指向Presenter并且手動的來更新View,如果使用綁定的話,MVVM代碼量将會小的多。

“MVVM很誘人,因為它集合了上述方法的優點,并且由于在View層的綁定,它并不需要其他附加的代碼來更新View,盡管這樣,可測試性依然很強。”

代碼示例

MVVMViewController.m

#import "MVVMViewController.h"
#import "MVVMView.h"
#import "MVVMModel.h"
#import "MVVMViewModel.h"
@interface MVVMViewController ()

@end

@implementation MVVMViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    MVVMView *MView = [MVVMView new];

    MVVMModel *model = [MVVMModel new];
    model.name = @"name1";
    MVVMViewModel *viewModel = [MVVMViewModel new];

    [self.view addSubview:MView];

    //* viewModel 作為樞紐 溝通view和model之間關系
    [viewModel setWithModel:model];
    [MView setWithViewMoel:viewModel];
}
           

MVVMModel

#import <Foundation/Foundation.h>

@interface MVVMModel : NSObject
@property(nonatomic,copy)NSString *name;

@end
           

MVVMViewModel.h

#import "MVVMModel.h"
@interface MVVMViewModel : NSObject
//對應Model中name
@property(nonatomic,copy)NSString *nameStr;

@property(nonatomic,strong)MVVMModel *model;

-(void)setWithModel:(MVVMModel *)model;
-(void)clickChangeName;
           

MVVMView.m 利用KVO監測值變化

// 隻是對controller 的拆分 不是真正view
//不可重用,真正的view 是這裡面的子View 可以重用

#import "MVVMView.h"
#import "NSObject+FBKVOController.h"
@interface MVVMView ()

@property(nonatomic,strong)MVVMViewModel *vm;
@property(nonatomic,strong)UILabel *label;
@property(nonatomic,strong)UIButton *button;

@end
@implementation MVVMView
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.backgroundColor = [UIColor whiteColor];
        self.frame = [UIScreen mainScreen].bounds;

        self.label = [[UILabel alloc]initWithFrame:CGRectMake(, , , )];
        self.label.backgroundColor = [UIColor orangeColor];
        [self addSubview:_label];

        self.button = [UIButton new];
        _button.backgroundColor = [UIColor redColor];
        [_button setTitle:@"點選" forState:UIControlStateNormal];
        [_button addTarget:self action:@selector(mvvmClickChangModel) forControlEvents:UIControlEventTouchUpInside];
        _button.frame = CGRectMake(, , , );
        [self addSubview:_button];
    }
    return self;
}
-(void)setWithViewMoel:(MVVMViewModel *)vm{
    self.vm = vm;
    //KVO
    [self.vm addObserver:self forKeyPath:@"nameStr" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    self.label.text = vm.nameStr;

//    //* FBKVO 第三方庫
//    [self.KVOController observe:self.vm keyPath:@"nameStr" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
//        self.label.text = change[NSKeyValueChangeNewKey];
//    }];

}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:    (NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if ([keyPath isEqualToString:@"nameStr"]&&[change objectForKey:NSKeyValueChangeNewKey]) {
        NSNumber *new = [change objectForKey:NSKeyValueChangeNewKey];
        self.label.text = [NSString stringWithFormat:@"%@",new];
    }
}
-(void)mvvmClickChangModel{
    [self.vm clickChangeName];
}
-(void)dealloc{
    [self.vm removeObserver:self forKeyPath:@"nameStr"];
}
           

RAC學習資料