iOS - MVC 架构模式

一、MVC

  • 从字面意思来理解,MVC 即 Modal View Controller(模型 视图 控制器),是 Xerox PARC 在 20 世纪 80 年代为编程语言 Smalltalk-80 发明的一种软件设计模式,至今已普遍应用于用户交互应用程序中。其用意在于将数据与视图分离开来。在 iOS 开发中 MVC 的机制被使用的淋漓尽致,充分理解 iOS 的 MVC 模式,有助于咱们程序的组织合理性。前端

  • MVC 的几个明显的特征和体现:
    • View 上面显示什么东西,取决于 Model。
    • 只要 Model 数据改了,View 的显示状态会跟着更改。
    • Control 负责初始化 Model,并将 Model 传递给 View 去解析展现。ios

    • 1)Modal 模型对象:编程

      • 模型对象封装了应用程序的数据,并定义操控和处理该数据的逻辑和运算。例如,模型对象多是表示商品数据 list。用户在视图层中所进行的建立或修改数据的操做,经过控制器对象传达出去,最终会建立或更新模型对象。模型对象更改时(例如经过网络链接接收到新数据),它通知控制器对象,控制器对象更新相应的视图对象。
    • 2)View 视图对象:swift

      • 视图对象是应用程序中用户能够看见的对象。视图对象知道如何将本身绘制出来,可能对用户的操做做出响应。视图对象的主要目的就是显示来自应用程序模型对象的数据,并使该数据可被编辑。尽管如此,在 MVC 应用程序中,视图对象一般与模型对象分离。设计模式

      • 在iOS应用程序开发中,全部的控件、窗口等都继承自 UIView,对应 MVC 中的 V。UIView 及其子类主要负责 UI 的实现,而 UIView 所产生的事件均可以采用委托的方式,交给 UIViewController 实现。数组

    • 3)Controller 控制器对象:网络

      • 在应用程序的一个或多个视图对象和一个或多个模型对象之间,控制器对象充当媒介。控制器对象所以是同步管道程序,经过它,视图对象了解模型对象的更改,反之亦然。控制器对象还能够为应用程序执行设置和协调任务,并管理其余对象的生命周期。mvc

      • 控制器对象解释在视图对象中进行的用户操做,并将新的或更改过的数据传达给模型对象。模型对象更改时,一个控制器对象会将新的模型数据传达给视图对象,以便视图对象能够显示它。app

      • 对于不一样的 UIView,有相应的 UIViewController,对应 MVC 中的 C。例如在 iOS 上经常使用的 UITableView,它所对应的 Controller 就是UITableViewController。异步

1.1 简单的 MVC

  • 控制器加载模型数据并将数据转换为数据模型。
  • 控制器建立视图控件,并将模型数据传递给视图控件

    MVC4

1.2 iOS MVC 示意图

MVC1

  • 1)Model 和 View 永远不能相互通讯,只能经过 Controller 传递。

  • 2)Controller 能够直接与 Model 对话(读写调用 Model),Model 经过 Notification 和 KVO 机制与 Controller 间接通讯。

  • 3)Controller 能够直接与 View 对话,经过 outlet,直接操做 View,outlet 直接对应到 View 中的控件,View 经过 action 向 Controller 报告事件的发生(如用户 Touch 我了)。Controller 是 View 的直接数据源(数据极可能是 Controller 从 Model 中取得并通过加工了)。Controller 是 View 的代理(delegate),以同步 View 与 Controller。

1.3 苹果推荐的 MVC -- 愿景

MVC2

  • Cocoa MVC

    • 因为 Controller 是一个介于 View 和 Model 之间的协调器,因此 View 和 Model 之间没有任何直接的联系。Controller 是一个最小可重用单元,这对咱们来讲是一个好消息,由于咱们总要找一个地方来写逻辑复杂度较高的代码,而这些代码又不适合放在 Model 中。

    • 理论上来说,这种模式看起来很是直观,但你有没有感到哪里有一丝诡异?你甚至据说过,有人将 MVC 的缩写展开成 (Massive View Controller),更有甚者,为 View controller 减负也成为 iOS 开发者面临的一个重要话题。若是苹果继承而且对 MVC 模式有一些进展,全部这些为何还会发生?

1.4 苹果推荐的 MVC -- 事实

MVC3

  • Realistic Cocoa MVC

    • Cocoa 的 MVC 模式驱令人们写出臃肿的视图控制器,由于它们常常被混杂到 View 的生命周期中,所以很难说 View 和 ViewController 是分离的。尽管仍能够将业务逻辑和数据转换到 Model,可是大多数状况下当须要为 View 减负的时候咱们却无能为力了,View 的最大的任务就是向 Controller 传递用户动做事件。ViewController 再也不承担一切代理和数据源的职责,一般只负责一些分发和取消网络请求以及一些其余的任务。

    • 你可能会看见过不少次这样的代码:

      BookModel *bookModel = [myDataArray objectAtIndex:indexPath.row];
          [cell configWithModel:bookModel];
      • 这个 cell,正是由 View 直接来调用 Model,因此事实上 MVC 的原则已经违背了,可是这种状况是一直发生的甚至于人们不以为这里有哪些不对。若是严格遵照 MVC 的话,你会把对 cell 的设置放在 Controller 中,不向 View 传递一个 Model 对象,这样就会大大减小 Controller 的体积。Cocoa 的 MVC 被写成 Massive View Controller 是不无道理的。
    • 直到进行单元测试的时候才会发现问题愈来愈明显。由于你的 ViewController 和 View 是紧密耦合的,对它们进行测试就显得很艰难--你得有足够的创造性来模拟 View 和它们的生命周期,在以这样的方式来写 View Controller 的同时,业务逻辑的代码也逐渐被分散到 View 的布局代码中去。

1.5 MVC 自身的不足

  • MVC 是一个用来组织代码的权威范式,也是构建 iOS App 的标准模式。Apple 甚至是这么说的。在 MVC 下,全部的对象被归类为一个 model,一个 view,或一个 controller。Model 持有数据,View 显示与用户交互的界面,而 View Controller 调解 Model 和 View 之间的交互。然而,随着模块的迭代咱们愈来愈发现 MVC 自身存在着不少不足。

  • 1)MVC 在现实应用中的不足:

    • 在 MVC 模式中 view 将用户交互通知给控制器。view 的控制器经过更新 Model 来反应状态的改变。Model(一般使用 Key-Value-Observation)通知控制器来更新他们负责的 view。大多数 iOS 应用程序的代码使用这种方式来组织。
  • 2)愈发笨重的 Controller:

    • 在传统的 app 中模型数据通常都很简单,不涉及到复杂的业务数据逻辑处理,客户端开发受限于它自身运行的的平台终端,这一点注定使移动端不像 PC 前端那样可以处理大量的复杂的业务场景。然而随着移动平台的各类深刻,咱们不得不考虑这个问题。传统的 Model 数据大多来源于网络数据,拿到网络数据后客户端要作的事情就是将数据直接按照顺序画在界面上。随着业务的愈来愈来的深刻,咱们依赖的 service 服务可能在大多时间没法第一时间知足客户端须要的数据需求,移动端愈发的要自行处理一部分逻辑计算操做。这个时间一惯的作法是在控制器中处理,最终致使了控制器成了垃圾箱,愈来愈不可维护。

    • 控制器 Controller 是 app 的 “胶水代码”,协调模型和视图之间的全部交互。控制器负责管理他们所拥有的视图的视图层次结构,还要响应视图的 loading、appearing、disappearing 等等,同时每每也会充满咱们不肯暴露的 Model 的模型逻辑以及不肯暴露给视图的业务逻辑。这引出了第一个关于 MVC 的问题...

    • 视图 view 一般是 UIKit 控件(component,这里根据习惯译为控件)或者编码定义的 UIKit 控件的集合。进入 .xib 或者 Storyboard 会发现一个 app、Button、Label 都是由这些可视化的和可交互的控件组成。View 不该该直接引用 Model,而且仅仅经过 IBAction 事件引用 controller。业务逻辑很明显不纳入 view,视图自己没有任何业务。

    • 厚重的 View Controller 因为大量的代码被放进 viewcontroller,致使他们变的至关臃肿。在 iOS 中有的 view controller 里绵延成千上万行代码的事并非前所未见的。这些超重 app 的突出状况包括:厚重的 View Controller 很难维护(因为其庞大的规模);包含几十个属性,使他们的状态难以管理;遵循许多协议(protocol),致使协议的响应代码和 controller 的逻辑代码混淆在一块儿。

    • 厚重的 view controller 很难测试,无论是手动测试或是使用单元测试,由于有太多可能的状态。将代码分解成更小的多个模块一般是件好事。

  • 3)太过于轻量级的 Model:

    • 早期的 Model 层,其实就是若是数据有几个属性,就定义几个属性,ARC 普及之后咱们在 Model 层的实现文件中基本上看不到代码(无需再手动管理释放变量,Model 既没有复杂的业务处理,也没有对象的构造,基本上 .m 文件中的代码广泛是空的);同时与控制器的代码越来厚重造成强烈的反差,这一度让人不由对现有的开发设计构思有所怀疑。
  • 4)遗失的网络逻辑:

    • 苹果使用的 MVC 的定义是这么说的:全部的对象均可以被归类为一个 Model,一个 view,或是一个控制器。就这些,那么把网络代码放哪里?和一个 API 通讯的代码应该放在哪儿?

    • 你可能试着把它放在 Model 对象里,可是也会很棘手,由于网络调用应该使用异步,这样若是一个网络请求比持有它的 Model 生命周期更长,事情将变的复杂。显然也不该该把网络代码放在 view 里,所以只剩下控制器了。这一样是个坏主意,由于这加重了厚重控制器的问题。那么应该放在那里呢?显然 MVC 的 3 大组件根本没有适合放这些代码的地方。

  • 5)较差的可测试性

    • MVC 的另外一个大问题是,它不鼓励开发人员编写单元测试。因为控制器混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。大多数人选择忽略这个任务,那就是不作任何测试。

    • 上文提到了控制器能够管理视图的层次结构;控制器有一个 “view” 属性,而且能够经过 IBOutlet 访问视图的任何子视图。当有不少 outlet 时这样作不易于扩展,在某种意义上,最好不要使用子视图控制器(child view controller)来帮助管理子视图。在这里有多个模糊的标准,彷佛没有人能彻底达成一致。貌似不管如何,view 和对应的 controller 都牢牢的耦合在一块儿,总之,仍是会把它们当成一个组件来对待。Apple 提供的这个组件一度以来在某种程度误导了大多初学者,初学者将全部的视图所有拖到 xib 中,链接大量的 IBoutLet 输出口属性,都是一些列问题。

二、MVC 的使用

  • Modal 模型的建立

    • Objective-C

      // BookModel.h
      
              @interface BookModel : NSObject
      
              // 根据须要使用的数据建立数 Modal 数据模型属性变量
              @property(nonatomic, copy)NSString *title;              
              @property(nonatomic, copy)NSString *detail;
              @property(nonatomic, copy)NSString *icon;
              @property(nonatomic, copy)NSString *price;
      
              + (instancetype)bookModelWithDict:(NSDictionary *)dict;
      
              @end
      
          // BookModel.m
      
              @implementation BookModel
      
              + (instancetype)bookModelWithDict:(NSDictionary *)dict {
      
              BookModel *book = [[self alloc] init];
                  [book setValuesForKeysWithDictionary:dict];
                  return book;
              }
      
              @end
    • Swift

      // BookModel.swift
      
              class BookModel: NSObject {
      
                  // 根据须要使用的数据建立数 Modal 数据模型属性变量
                  var title:String?
                  var detail:String?
                  var icon:String?
                  var price:String?
              }
  • View 视图的建立

    • Objective-C

      // BookCell.h
      
              @class BookModel;
      
              @interface BookCell : UITableViewCell
      
              // 建立 Cell 视图包含的内容,Cell 使用 xib 建立 
              @property (weak, nonatomic) IBOutlet UIImageView *iconView;         
              @property (weak, nonatomic) IBOutlet UILabel *titleLabel;
              @property (weak, nonatomic) IBOutlet UILabel *detailLabel;
              @property (weak, nonatomic) IBOutlet UILabel *priceLabel;
      
              // 建立 Cell 视图赋值方法
              @property (nonatomic, strong) BookModel *bookModel;
      
              @end
      
          // BookCell.m
      
              // 包含数据模型头文件
              #import "BookModel.h"                                   
      
              @implementation BookCell
      
              // 从 Model 数据模型中取出数据更新 View 的内容
              - (void)setBookModel:(BookModel *)bookModel {
      
                  _iconView.image = [UIImage imageNamed:bookModel.icon];
                  _titleLabel.text = bookModel.title;
                  _detailLabel.text = bookModel.detail;
                  _priceLabel.text = bookModel.price;
              }
      
              @end
    • Swift

      // BookCell.swift
      
              class BookCell: UITableViewCell {
      
                  // 建立 Cell 视图包含的内容,Cell 使用 xib 建立 
                  @IBOutlet weak var iconView: UIImageView!
                  @IBOutlet weak var titleLabel: UILabel!
                  @IBOutlet weak var detailLabel: UILabel!
                  @IBOutlet weak var priceLabel: UILabel!
      
                  // 建立 Cell 视图赋值方法,从 Modal 数据模型中取出数据更新 View 的内容
                  func configWithModel(bookModel:BookModel) {
      
                      iconView!.image = UIImage(named: bookModel.icon!)
                      titleLabel!.text = bookModel.title
                      detailLabel!.text = bookModel.detail
                      priceLabel!.text = bookModel.price
                  }
              }
  • Controller 控制器的建立

    • Objective-C

      // ViewController.m
      
              // Modal 模型处理
      
                  // 声明数据源
                  @property (nonatomic, strong) NSArray *myDataArray; 
      
                  // 加载模型数据
                  - (NSArray *)myDataArray {
      
                      if (_myDataArray == nil) {
      
                          NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"bookData" 
                                                                                                            ofType:@"plist"]];
      
                          NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:array.count];
                          [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
      
                              // KVC
                              BookModel *bookModel = [BookModel bookModelWithDict:obj];                               
                              // 使用 Modal 数据模型初始化数据源数组
                              [arrayM addObject:bookModel];
                          }];
      
                          _myDataArray = [arrayM copy];
                      }
                      return _myDataArray;
                  }
      
              // View 视图处理
      
                  UITableView *myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 20, self.view.bounds.size.width, 
                                                                                          self.view.bounds.size.height - 20)];
                  myTableView.delegate = self;
                  myTableView.dataSource = self;
                  [myTableView registerNib:[UINib nibWithNibName:@"BookCell" bundle:nil] forCellReuseIdentifier:@"BookCell"];
                  [self.view addSubview:myTableView];
      
                  - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
                      return [self.myDataArray count];
                  }
      
                  - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
                      return 80;
                  }
      
                  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
      
                      BookCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BookCell" forIndexPath:indexPath];
      
                  // 从 Modal 数据模型中取出数据更新 View 的内容
                      cell.bookModel = self.myDataArray[indexPath.row];
      
                      return cell;
                  }
    • Swift

      // ViewController.swift
      
              // Modal 模型处理
      
                  var myDataArray:NSMutableArray!
      
                  myDataArray = NSMutableArray()
      
                  for bookInfoDic in NSArray(contentsOfFile: NSBundle.mainBundle().pathForResource("bookData", ofType: "plist")!)! {
      
                      let bookModel = BookModel()
      
                      // KVC
                      bookModel.setValuesForKeysWithDictionary(bookInfoDic as! Dictionary)
      
                      // 使用 Modal 数据模型初始化数据源数组
                      myDataArray.addObject(bookModel)
                  }
      
              // View 视图处理
      
                  let myTableView:UITableView = UITableView(frame: CGRectMake(0, 20, self.view.bounds.size.width, 
                                                                                     self.view.bounds.size.height - 20))
                  myTableView.delegate = self
                  myTableView.dataSource = self
                  myTableView.registerNib(UINib(nibName: "BookCell", bundle: nil), forCellReuseIdentifier: "BookCell")
                  self.view.addSubview(myTableView)
      
                  func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                      return myDataArray.count
                  }
      
                  func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
                      return 80
                  }
      
                  func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
      
                      let cell = tableView.dequeueReusableCellWithIdentifier("BookCell", forIndexPath: indexPath) as! BookCell
      
                      let bookModel:BookModel = myDataArray.objectAtIndex(indexPath.row) as! BookModel
                      cell.configWithModel(bookModel)
      
                      // 从 Modal 数据模型中取出数据更新 View 的内容
                      return cell
                  }
相关文章
相关标签/搜索