MVVM设计模式教程 - tutorial with ReactiveCocoa

来源:互联网 发布:linux squid 启动 编辑:程序博客网 时间:2024/04/30 18:49
你可能在Twitter上看到过这样的笑话:
“iOS MVC架构,代表着杂乱无序的ViewController” - Colin Campbell
我敢确定大家开发中都碰到过,过大的、不可维护的view controller。


MVVM使用不同的架构模式,model-view-view model,简称MVVM。


这种模式,通过反射获得灵活性,是一个优秀的MVC替换模式,保证获得平滑的、轻量的view controller。


该MVVM教程从一个简单的flickr搜索应用开始,





在本教程开始前,我们回顾一下一些技术原理。


ReactiveCocoa扼要回顾


本教程是关于MVVM的重要文章,需要读者有一些ReactiveCocoa知识。
假如没有使用过ReactiveCocoa,强烈建议翻读我之前的ReactiveCocoa博客。


再次回顾一下ReactiveCocoa的要点,使读者对ReactiveCocoa有个快速概览。


ReactiveCocoa的核心是Signals,核心类RACSignal,Signals触发事件流(events stream), 包括类型,下一步,完成,以及错误等等。


使用这个简单的模式,ReactiveCocoa可以扮演委托模式(delegate pattern),行为目标模式(target-action patter), key-value模式,等等。


Signal API 代码干净,简单易读,而且在上层代码的很多操作中可以使用这些Signal。
你可以实现复杂的过滤器,发送模块,条件判断等高级设计模型。


在MVVM的上下文中,ReactiveCocoa扮演非常特俗的角色。它在ViewModel和View之间像提供了类似‘胶水’功能。


Model-View-ViewModel,即MVVM,是一个UI设计模式,是设计模式MV*家族中的一员,这些成员包括MVC,MVP(Model View Presenter),...。


上述的每个设计模式关注的是UI层和逻辑层(business logic)分离,为了使程序便于开发和测试。


Note:关于设计模式,我推荐Eli和Ash Furrow的文章。


为了更好理解MVVM,我们再看一下MVC。


MVC是第一个UI设计模式,它的原型来自70年代的SmallTalk语言。下面的图片展示了MVC模块的交互。




这个模式,Model维护着程序状态,UI控件、视图组成了View,而Controller处理了用户交互(Interactions),承担更新Model,View的事情。


一个大的问题关于MVC模式是太过模糊。原理看似简单,使用起来经常会使Model, View, Controller循环依赖。总之,越变越大,杂乱无章。


最近Martin Fowler介绍了MVC-Presentation Model, 就是采用了,并且微软也是倍加推崇的MVVM模式。


MVVM设计模式


MVVM模式核心模块是ViewModel,这个特殊的model表示了应用程序的UI状态。
它包含了每个UI控件,视图的状态(state)和属性。比如,它包含了TextField的文本(text), 或者某个Button的enable属性,同样包含了视图(view)可以操作的事件(action),点击(tap),手势(gesture)。


可以认为ViewModel是视图的模型(Model-of-the-view)。




MVVM的三个组件和MVC的类似,遵循以下精确的规则,
1. 视图(View)有一个ViewModel的引用,相反没有;
2. ViewModel有一个Model的引用,相反没有;
以上两点需要一起执行,
MVVM显而易见的优点是,
1. 视图层更加轻,UI的逻辑都在ViewModel中;
2. 测试更加方便,应用拿掉UI也可以运行;


由于ViewModel没有View的引用,那如何更新View?重点来了,


MVVM 和 数据绑定(Data Binding)
MVVM模式依靠数据绑定(Data Binding), 一个框架层面的特性自动连接对象属性到UI控件。





例如,微软的WPF框架,下面的配置把TextField的Text属性和ViewModel中的Username绑定
<TextField Text=”{DataBinding Path=Username, Mode=TwoWay}”/>


TwoWay模块需要确保Username的改变能够传播到TextField的Text属性中,相反,用户输入时需要更新Username。


另外一个例子是Knockout(基于Web的MVVM框架),绑定的设置,
<input data-bind=”value: username”/>
一个HTML的元素和JavaScript模块中的元素绑定。


不幸的是iOS还缺少数据绑定框架,那么我们就用ReactiveCocoa来扮演这个角色。


从iOS开发的角度来看MVVM,ViewController和相关的UI-不管是nib,storyboard还是代码实现的,组成了View,


ReactiveCocoa绑定了View和ViewModel。


Note:推荐Martin Fowler的GUI Architectures article


Okey,我们开始创建一个ViewModel


Starter Project Sturecture


先下载初学者工程

  • FlickrSearchStarterProject.zip

它用CocoaPods来管理依赖。


这个工程已经包含了应用程序的ViewController和nib文件组成的视图模块。
打开RWTFlickrSearch.xcworkspace。


花点时间熟悉一下工程结构,





第一个ViewModel


添加一个新类,命名为RWTFlickrSearchViewModel,继承自NSObject,


@interface RWTFlickrSearchViewModel : NSObject
 
@property (strong, nonatomic) NSString *searchText;
@property (strong, nonatomic) NSString *title;
 
@end


searchText属性代表者页面上TextField的text属性,title代表了导航栏上的标题。


Note:为了更好理解应用结构,View和ViewModel取相同的名称,带不同的后缀,RWTFlickrSearch-ViewModel和RWTFlickrSearch-ViewConrtoller。


@implementation RWTFlickrSearchViewModel
 
- (instancetype)init {
  self = [super init];
  if (self) {
    [self initialize];
  }
  return self;
}
 
- (void)initialize {
  self.searchText = @"search text";
  self.title = @"Flickr Search";
}
 
@end


代码很简单。


下一步是ViewModel和View关联。记住视图模块(View)有一个ViewModel的引用。看起来Viewmodel的初始化和视图模块关联起来也比较合理。


#import "RWTFlickrSearchViewModel.h"


@interface RWTFlickrSearchViewController : UIViewController 
- (instancetype)initWithViewModel:(RWTFlickrSearchViewModel *)viewModel;
 
@end


在RWTFlickrSearchViewContrller.m中,添加一个私有的属性,


@property (weak, nonatomic) RWTFlickrSearchViewModel *viewModel;
- (instancetype)initWithViewModel:(RWTFlickrSearchViewModel *)viewModel {
  self = [super init];
  if (self ) {
    _viewModel = viewModel;
  }
  return self;
}


在View层保存一个ViewModel的弱引用。


在viewDidLoad函数中添加:


// 初始化绑定
[self bindViewModel];


- (void)bindViewModel {
  self.title = self.viewModel.title;
  self.searchTextField.text = self.viewModel.searchText;
}


刚才提到的是在ViewController中保存了一个弱引用,那么在哪里拥有实例呢?
这个例子中提到的是AppDelegate中,


在RWTAppDelegate.m中,


#import "RWTFlickrSearchViewModel.h"
@property (strong, nonatomic) RWTFlickrSearchViewModel *viewModel;


看看初始化函数,


- (UIViewController *)createInitialViewController {
  self.viewModel = [RWTFlickrSearchViewModel new];
  return [[RWTFlickrSearchViewController alloc] initWithViewModel:self.viewModel];
}





ViewModel文件已经添加了,下面该ReactiveCocoa登场了。


Detecting Vaild Search State


接下来我们需要ReactiveCocoa把ViewModel和View绑定在一起了。
在RWTFlickrSearchViewController.m中,更新bindViewModel,


- (void)bindViewModel {
  self.title = self.viewModel.title;
  RAC(self.viewModel, searchText) = self.searchTextField.rac_textSignal;
}


添加一个rac_textSignal属性到UITextField类中,需要ReactiveCocoa来扩展。
当textfield更新后,就会触发包含当前text的一个事件(rac_textSignal)。


RAC宏就是viewModel中searchText和TextField rac_textSignal事件的一个绑定。


简而言之,确保searchText属性总是反映当前UI(TextField)的状态。


搜索按钮的enable状态和text输入是否有效关联在一起。在RWTFlickrSearchViewModel.m添加,


#import <ReactiveCocoa/ReactiveCocoa.h>
- (void)initialize {
  self.title = @"Flickr Search";
 
  RACSignal *validSearchSignal =
    [[RACObserve(self, searchText)
      map:^id(NSString *text) {
         return @(text.length > 3);
      }]
      distinctUntilChanged];
 
  [validSearchSignal subscribeNext:^(id x) {
    NSLog(@"search text is valid %@", x);
  }];
}


添加搜索命令


@property (strong, nonatomic) RACCommand *executeSearch;


- (void)initialize {
  self.title = @"Flickr Search";
 
  RACSignal *validSearchSignal =
    [[RACObserve(self, searchText)
      map:^id(NSString *text) {
         return @(text.length > 3);
      }]
      distinctUntilChanged];
 
  [validSearchSignal subscribeNext:^(id x) {
    NSLog(@"search text is valid %@", x);
  }];

self.executeSearch =
  [[RACCommand alloc] initWithEnabled:validSearchSignal
    signalBlock:^RACSignal *(id input) {
      return  [self executeSearchSignal];
    }];
}


- (RACSignal *)executeSearchSignal {
  return [[[[RACSignal empty]
           logAll]
           delay:2.0]
           logAll];
}


现在该搜索按钮绑定了,
在RWTFlickrSearchViewController.m中


- (void)bindViewModel {
  self.title = self.viewModel.title;
  
  // 属性绑定
  RAC(self.viewModel, searchText) = self.searchTextField.rac_textSignal;
  
  // 搜索事件绑定
  self.searchButton.rac_command = self.viewModel.executeSearch;
}


Model在哪里?


到这里,你可能有清晰认识到View(RWTFickrSearchViewController)和ViewModel(RWTFlickrSearchViewModel),那么Model在哪里?


这个列子中,用Search TextField中的text搜索,并且返回一组匹配的结果。
你可以在ViewModel中添加逻辑模块。


RWTFlickrSearch.m提供接口定义,


#import <ReactiveCocoa/ReactiveCocoa.h>
@import Foundation;
 
@protocol RWTFlickrSearch <NSObject>
 
- (RACSignal *)flickrSearchSignal:(NSString *)searchString;
 
@end


RWTFlickrSearchImpl提供了实现部分,


@import Foundation;
#import "RWTFlickrSearch.h"
 
@interface RWTFlickrSearchImpl : NSObject <RWTFlickrSearch>
 
@end


implementation:
@implementation RWTFlickrSearchImpl
 
- (RACSignal *)flickrSearchSignal:(NSString *)searchString {
  return [[[[RACSignal empty]
            logAll]
            delay:2.0]
            logAll];
}
 
@end


RWTFlickrSearch详细的例子,方方面面都有涉及,请参考,

 example project


结论


  • MVVM设计模式获得流行;
  • MVVM模式可以把UI做的很轻很薄,增强了逻辑功能的可测性;
  • 精确的规则View => ViewModel => Model,ViewModel和View之间用绑定实现。
  • ViewModel不会持有View的引用,增强了View的可替换性。
  • ViewModel可以想像成model-of-the-view, 它暴露了反映View的属性,还有用户交互的方法。
  • Model层典型地用服务方式(Service)实现。
  • 一个很好的MVVM应用是可以没有UI层就可以运行。
  • ReactiveCocoa提供了很好的机制,使ViewModel和View绑定。

原文地址:
http://www.raywenderlich.com/74106/mvvm-tutorial-with-reactivecocoa-part-1
http://www.raywenderlich.com/74106/mvvm-tutorial-with-reactivecocoa-part-2


0 0
原创粉丝点击