美文网首页学无止境在iOS开发的道路上越走越远上海恩美路演
[译]基于ReactiveCocoa的MVVM开发模式教程:Pa

[译]基于ReactiveCocoa的MVVM开发模式教程:Pa

作者: x_code | 来源:发表于2015-12-16 22:49 被阅读783次

    此文是翻译作品,原文见:http://www.raywenderlich.com/74106/mvvm-tutorial-with-reactivecocoa-part-1

    此文是我学习过程中遇到的很好的文章,因为搜不到翻译版本,因此自己翻译了,希望能帮到大家。同时翻译的时候我也好好精进了一下我的markdown语法

    你可能在Twitter上听过这样的笑话:

    “iOS框架,大量View Controller的产生地” by Colin Campbell

    这在iOS开发者心中是个轻松地“戳”,但是我确信你已经在练习中遇到过这些问题了——臃肿的,难以管理的View Controller。

    这个MVVM的开发教程用一个不同的模式来构建一个app,Model-View-View-ViewModel,或者简称MVVM,这个模式因为ReactiveCocoa的诞生更加方便,带来了一个完美的MVC模式的替换模式,和一个轻便的,易于管理的View Controller!

    通过这个MVVM教程,你要去建立一个简单的搜索app叫做Flicker search,像下面的图片一样:

    1.png

    注意:这个教程是使用Objective-C开发的,如果你要看我用swift开发的教程,点击这里,在我的博客里面可以看到。

    在你开始写代码前,是时候讲一些理论知识了!

    一个对ReactiveCocoa的简单介绍

    这个教程主要是关于MVVM的,并且假设你对ReactiveCocoa有一定的了解,如果你没有用过ReactiveCocoa,我强烈建议你看我早一些的教程,这个教程会教给你很多。
    ReactiveCocoa最核心的东西无疑是 signals,在RACSignal 这个类里面。signals给事件发出一个流,这个流(stream)有三种类型: nextcompletederror

    运用这些简单的模式,ReactiveCocoa 可以用来替代代理模式(delegate pattern),观察者模式(KVO)和 target-action pattern,以及更多。

    用signal的API编写出来的代码更加均匀,因此更加容易阅读,但是ReactiveCocoa真正的强大的地方在于是你对signals的高级操作,这些操作允许你进行复杂的过滤(filter),转化(transformation)以及用简单的方式协调(coordination)。

    在MVVM的环境下,ReactiveCocoa扮演了极其重要的角色,它提供了强大的粘合力在View和ViewModel中间,这些对你还有一点点的超前。

    MVVM开发模式的介绍

    MVVM——Model-View-ViewModel,在通常的理解中是一个设计的模式,他是MV家族的一个成员,这个家族包括MVC、MVP等等。

    每一个MV家族中的模式开始关心如何将UI和业务逻辑分开,因为这样更便利于开发和测试。

    注意:如果想要深入了解开发设计模式,我推荐Eli’sAsh Furrow’s的文章。

    了解MVVM的起源有助于你更加了解这个模式。

    MVC是第一个用户界面设计模式( UI design pattern),可以追溯到1970年代的Smalltalk language。下面这个图说明了MVC的主要运作模式:

    MVC.png

    这个模式将用户界面分为三种:

    • Model,用来呈现应用状态。
    • View,由视图控制器组成。
    • Controller,处理用户交互并且更新model。

    MVC的一个重大问题令人十分困扰,这个概念很好很完美,但是当经常人们开始实现MVC的时候,Model-View-Controller看似圆形的关系,反过来,他们合并成了一个可怕的巨大的麻烦。

    不久之前Martin Fowler 向我们介绍了一个由MVC衍生出来的表现模式,并被微软接受并流行开来。

    MVVM.png

    这个模式的核心是ViewModel,是一种特殊的Model,用来展示应用中UI的状态。

    它包含了每一个UI控制器(Controller)的详细状态和属性,例如,一个TextFeild当前的文字,或者一个按钮的可否点击的状态,它也展现了当前视图的一系列动作,例如按钮点击或者手势操作。

    将VIewModel理解成为View的Model(model of the view)可以更好地帮你去思考ViewModel。

    MVVM遵循以下规则

    • 1.View用来展现VIewModel,但是VIewModel不能展现View。
    • 2.VIewModel用来展现Model,但是也不可反过来。

    如果你打破了任何这个规则,你的MVVM就错了!

    这种规则的优势如下:

    • 1.更加轻量级的VIew层,所有业务逻辑都被移到ViewModel中。
    • 2.更易于测试,你可以在没有View的情况下启动你的应用,大大提高了可测试性。

    注意:测试视图是众所周知的困难,因为测试运行的小的包含的代码块。通常,控制器会在依赖于其他应用程序状态的场景中添加和配置视图。这意味着,意义上的小测试,可以成为一个脆弱而繁琐的命题。

    因此,你可能会想提出一个问题,如果只是View可以展现VIewModel,而ViewModel不能反过来展现View的话,那么ViewModel如何更新View呢?啊哈!!这就是MVVM的秘诀了!

    MVVM和数据绑定(Data Binding)

    MVVM模式依赖于数据绑定,一个框架级的功能,自动连接对象属性的用户界面控件。

    有一个例子,在微软的WPF框架,下面一个例子将TextField的文本和ViewModel的Username绑定。

      <TextField Text=”{DataBinding Path=Username,Mode=TwoWay}”/>
    

    WPF的框架将这两个成员变量“绑定”。

    这个双向的绑定确保了ViewModel的Username的改变同时TextFeild的文本也改变,反之亦然,用户的输入也将改变ViewModel中的参数值。

    另一个例子,基于web的流行的一个MVVM框架Knockout, 你可以发现两个框架中数据绑定的相同的特点。

    <input data-bind=”value: username”/>
    

    上面的绑定将HTML的元素和JavaScript的模型绑定。

    不幸的是,iOS缺少一个数据绑定框架,但是这就是ReactiveCocoa所充当的“胶水”作用。

    具体从iOS开发的角度去看MVVM,ViewController和它相关的UI——不论是xib、storyboard或者是代码组成的视图(View):

    MVVMReactiveCocoa.png

    ReactiveCocoa将两者绑定起来。

    注意:对于UI的各种实现方式,我高度推荐Martin Fowler的GUI Architectures article

    你学到了足够的理论知识了吗?如果没有,请回头去再看一遍。当然,如果你学得够好了,那么现在是时候开始创造你自己的ViewModel了。

    开始项目准备

    首先下载这个开始工程

    这个项目使用CocoaPods去管理依赖库(如果你不知道CocoaPods,我们这里有个教程),运行pod install去安装依赖库,确认你看到了一下输出:

      $ pod install
      Analyzing dependencies
      Downloading dependencies
      Installing LinqToObjectiveC (2.0.0)
      Installing ReactiveCocoa (2.1.8)
      Installing SDWebImage (3.6)
      Installing objectiveflickr (2.0.4)
      Generating Pods project
      Integrating client project
    

    你会学到每个依赖库是干吗的。

    本教程的开始工程包含了一个View,通过xib实现,打开RWTFlickrSearch.xcworkspace,并且运行,然后你会看到以下页面:

    first-launch.jpg

    花一点时间去熟悉这个项目结构:


    EmptyInterface.png

    Model和VIewModel的groups都是空的,你要为这两个group添加文件,项目已含有的文件是做这些的:

    是时候开始写你的第一个view model 了!

    你的第一个ViewModel

    在ViewModel这个group里添加一个新的类,将之命名为RWTFlickrSearchViewModel并且使他继承NSObject

    打开它并在头文件添加下面的声明:

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

    searchText提供一个字符串显示在textfield上,成员变量title提供在navigation bar上显示的标题。

    注意 :为了更容易的理解项目结构,View和ViewModel用了相同的名字和不同的后缀,例如:RWTFlickrSearch-ViewModel
    RWTFlickrSearch-ViewController

    打开 RWTFlickrSearchViewModel.m 并且添加如下代码

      @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。

    下一步是讲如何将ViewModel和View关联到一起,记住View和ViewModel的关联,因此就需要在View中给对应的ViewModel添加一个相关的实例化方法。

    注意:在这个教程管我们的Controller叫做”Views“,这笔“View”在MVVM更多语义。和UIKit使用的默认名不同。

    打开 RWTFlickrSearchViewController.h 并声明ViewModel的头文件。
    #import "RWTFlickrSearchViewModel.h"
    然后加入下面的实例化方法

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

    RWTFlickrSearchViewController.m中添加一个私有变量

    @property (weak, nonatomic) RWTFlickrSearchViewModel *viewModel;
    

    接下来实现init方法

    - (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;
    }
    

    上面的代码将会在UI初始化和ViewModel状态在VIew上应用的时候运行。
    最后一步是实例化ViewModel,并在View中应用。
    viewDidLoad中添加以下

    #import "RWTFlickrSearchViewModel.h"
    

    加一个私有变量(在文件顶部的类扩展名内)。

    @property (strong, nonatomic) RWTFlickrSearchViewModel *viewModel;
    

    你会发现已经有了一个createInitialViewController方法,更新他的实现方法:

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

    这将创建一个新的ViewModel实例,然后它返回View。这是应用程序的导航控制器的初始视图。

    恭喜你,这是你的第一个ViewModel。我得请你控制住你的兴奋!这里还有很多要学习。
    你可能已经注意到了你没有使用任何ReactiveCocoa呢。在其目前的形式,任何用户进入搜索文本字段将不会反映在ViewModel。

    检测有效搜索状态

    在这一部分中,你将使用ReactiveCocoa绑定ViewModel和View的搜索框和按钮在一起,更新RWTFlickrSearchViewController.m中的bindViewModel方法如下

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

    我们来加一个ReactiveCocoa 中UITextFeild的分类的方法rac_textSignal,它是一个信号,每次文本字段更新时,将发出包含当前文本的一个事件,RAC宏是一个绑定,上面代码更新了ViewModel中的searchText对象的值,它随着rac_textSignal响应。
    总之,上面的代码保证了searchText的值总是UI中的最新的值。如果上面的写法让你感到陌生,你真的应该重新学习一下ReactiveCocoa tutorials这个教程!

    如果用户输入的文本是有效的,则只能启用搜索按钮。这里的输入规则是,他们必须输入超过三个字符,然后才能执行搜索。
    RWTFlickrSearchViewModel.m加入下面代码

    #import <ReactiveCocoa/ReactiveCocoa.h>
    

    更新方法initialize:

    - (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);
       }];
     }
    

    编译,运行并在TextFeild输入一些文字。每次文本在有效或无效状态之间转换时,都会看到日志消息:

    2014-05-27 18:03:26.299 RWTFlickrSearch[13392:70b] search text is valid 0
    2014-05-27 18:03:28.379 RWTFlickrSearch[13392:70b] search text is valid 1
    2014-05-27 18:03:29.811 RWTFlickrSearch[13392:70b] search text is valid 0
    

    上面的代码使用 RACObserve宏创建了一个ViewModel中的 searchText的信号。map操作将文本流转换为真值和假值。最后, distinctuntilchanges是用来确保该信号只在状态变化的时候传递值。

    相关文章

      网友评论

      • 付寒宇:非常期待能继续翻译下去,正在尝试在新项目中使用,感觉这个博主写的很好,可惜英文很不好。期待更新
      • x_code:未完待续,最近项目太忙

      本文标题:[译]基于ReactiveCocoa的MVVM开发模式教程:Pa

      本文链接:https://www.haomeiwen.com/subject/vavfhttx.html