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
                  }
相關文章
相關標籤/搜索