第二课:Applying MVC

关于斯坦福的这个系列课程的所有笔记,都在这个文集里:Developing Apps for iOS9

实际上课时间为:2016年3月30日。课程发布在iTunes上的时间是2016年4月23日, 课程地址。

一、MVC 模式

MVC模式详细的解释,讲了15分钟。

定义:
Model = What your application is (but not how it is displayed)

Controller = How your Model is presented to the user (UI logic)

View = Your Controller's minions

三大阵营的交流:

Controllers can always talk directly to their Model and their View. Controller interpret/format Model information for the View.

The Model and View should never speak to each other.

controller可以对view和model说话,controller当然也可以把model的信息翻译给view。不过model和view直接永远不说话。

那么view能对controller说话吗?只能在某些情况下:action,outlet,delegate,data source。

那么model能对controller说话吗?不能。那么model数据有了更新,怎么告诉controller数据有更新呢?通过使用 Notification&KVO 模式广播数据更新的事件。

二、计算器Demo应用

继续代码,增加功能。

1. 让读取写入数据更容易的方法

在 viewController 中优化一个方法:

     var displayValue:Double {
        get {
            return Double(display.text!)!
        }
        set {
            display.text = String(newValue)
        }
    }

2. 点击运算符的方法

计算器App上有数字Button,点击数字创建Action连接方法是@IBAction private func touchDigit(sender: UIButton),有π、e、加减乘除、等于这样的运算符,当点击这些运算符时,触发了@Action 方法:

    @IBAction private func performOperation(sender: UIButton) {
     //1. 把用户输入的数字传给 Model
     //2. 把用户点击选中的运算符(比如加减乘除中的某一个)传给Model
     //3. 读取把 Model 计算出来的结果,然后将结果显示到App的label上
     //注意:这里都是和Model交互,因为Controller只是翻译,记得吗,这些计算,实际上应该是MVC中的M来做的事情
    }

3. 创建 Model

新建 CalculatorBrain.swift 文件。
根据上面2中的3个注释,我们可以知道至少需要三个东西:

  import Foundation
  class CalculatorBrain {

    //1. 把用户输入的数字传给 Model
    func setOperand(operand: Double) { }

    //2. 把用户点击选中的运算符(比如加减乘除中的某一个)传给Model
    func performOperation(symbol: String) { }

    //3. (读取把 Model 计算出来的结果,然后将结果显示到App的label上) = 也就是要有结果,可读的结果
    var result: Double {
        get {
             return 0.0   
        }
     }
  }

有了这三个方法,点击某个运算符后的方法就可以扩充一下了:

    var brain = CalculatorBrain()
    @IBAction private func performOperation(sender: UIButton) {
        if userIsInTheMidlleOfTyping {

            // 把用户输入的数字传给 Model
            brain.setOperand(displayValue)
            // 正在输入数字的状态设为false
            userIsInTheMidlleOfTyping = false
        }
        
        if let mathematicalSymbol = sender.currentTitle {
            // 把用户点击选中的某个运算符(比如加减乘除中的某一个)传给Model
            brain.performOperation(mathematicalSymbol)           
        }
        // 读取把 Model 计算出来的结果,然后将结果显示到label里
        displayValue = brain.result
    }

4. API 知识

让 View controller 中的属性成为 private,在 view controller 文件里将变量和方法变成 private。

5. setOperand 方法 + 结果

点击运算符后已经将数字传给 Model 了,接下来在 Model 里实现具体的要求:

    // 创建局部变量,记录用户需要计算的数字
    private var accumulator = 0.0
    func setOperand(operand: Double) { 
        accumulator = operand
    }

最后的结果可以返回刚刚创建的局部变量了:

    var result: Double {
        get {
             return 0.0   
        }
     }

6. 完善 performOperation 方法之善用枚举

这个可是 CalculatorBrain 里最核心的部分了,进行计算的地方。那么不写代码,先理顺一下思路。

     func performOperation(symbol: String) { }

首先要做的事情就是,确认传过来的到底是哪个运算符。App上的运算符除了数字,都可能是这个symbol。那么可以用 switch 语句来切换。

确认了是哪个运算符之后就可以进行计算了,π还好说,∛这样的也容易,只需要一个数字就能计算了,加减乘除可怎么办呢?这需要首先要记住传入的第一个参数,然后记住运算符是哪个,然后知道第二个运算符,才能进行计算的。哦对了,别忘了还有等号这个按钮。

把运算符分分类发现,也就常量π、一元运算符cos、二元运算符+、等号这四类了。那么我们可以用枚举列出这四种情况:
swift private enum Operation { //常量 case Constant //一元运算符 case UnaryOperation //二元运算符 case BinaryOperation //等号 case Equals }

7. 完善 performOperation 方法之善用词典

也就是说,任何一个运算符都是这四个之一。每个运算符都对应着四个中的一个,可以用词典表示这样的情况,是的,词典的值可以是枚举。

  private var operations: Dictionary = [
      "cos" : Operation.UnaryOperation,
      "sin" : Operation.UnaryOperation,

      "×" : Operation.BinaryOperation,
      "÷" : Operation.BinaryOperation,
      "+" : Operation.BinaryOperation,
      "-" : Operation.BinaryOperation,
              
      "π" : Operation.Constant,
      "e" : Operation.Constant,
      "=" : Operation.Equals
  ]

但是这样还不能进行计算。

8. 完善 performOperation 方法之 associated value

常量的话,就是用户点击π,传入的数字就是π,不存在计算的问题。一元运算符,传入一个数字都就可以进行计算了,二元运算符需要进行计算,计算的方法必须要有两个参数,同时要有返回结果,当然,二元运算符需要有个容器能记住第一次传入的参数和用户点击的运算符。

让枚举有 associated value。(optional也是枚举,optional有 associated value)在Swift里,function也可以成为associated value。

  private enum Operation {
      // 如果是常量,只需Double类型的数值
      case Constant(Double)
      // 如果是一元运算符,则需要一个方法,这个方法获知1个参数,返回结果
      case UnaryOperation((Double) -> Double)
      // 如果是二元运算符,需要一个方法,这个方法是获知2个数值,返回结果
      case BinaryOperation((Double,Double) -> Double)
      case Equals
  }

9.完善 performOperation 方法之闭包Closure

枚举这里改了,词典那边会出现闭包,如下:



利用闭包默认参数$0$1可以省去一些代码:

  private var operations: Dictionary = [
      "cos" : Operation.UnaryOperation(cos),
      "sin" : Operation.UnaryOperation(sin),
      "tan" : Operation.UnaryOperation(tan),
      "±" : Operation.UnaryOperation({-$0}),
      "∛" : Operation.UnaryOperation(cbrt),
      "√" : Operation.UnaryOperation(sqrt),
      "X²": Operation.UnaryOperation({$0*$0}),
      "X³": Operation.UnaryOperation({$0*$0*$0}),
      
      "×" : Operation.BinaryOperation({$0 * $1}),
      "÷" : Operation.BinaryOperation({$0 / $1}),
      "+" : Operation.BinaryOperation({$0 + $1}),
      "-" : Operation.BinaryOperation({$0 - $1}),
      
      "%" : Operation.UnaryOperation({$0/100}),
      
      "π" : Operation.Constant(M_PI),
      "e" : Operation.Constant(M_E),
      "=" : Operation.Equals
  ]

10. 完善 performOperation 方法之使用 Associated Value

再回到核心代码上来,这些枚举、词典什么,都是为了实现 performOperation 方法的

    func performOperation(symbol: String) {      
      if let operationH = operations[symbol] {          
          switch operationH {
          // 这里的 let associatedValue 只是一个局部常量而已,可任意命名
          case .Constant(let associatedValue): accumulator = assosicatedValue
          // 这里的 function 只是一个局部常量而已,可任意命名       
          case .UnaryOperation(let function): accumulator = fuction(accumulator)
          case .BinaryOperation: break            
          case .Equals:  break              
          }
      }
  }

11. 完善 performOperation 方法之结构体

二元运算符的方法没有写,为啥呢?因为我们还没有创建能够存储第一次传入的值和选中的运算符,那就创建一个容器存储这两个东西吧,我们用结构体来存储:

  private var pending: PendingBinaryOperationInfo?
  struct PendingBinaryOperationInfo {
      var binaryFunction:(Double, Double) -> Double
      var firstOperand: Double
  }

这样就可以继续 performOperation 方法了(把第一个传入的参数和用户选中的运算符存放到了 pending 中):

    func performOperation(symbol: String) {      
      if let operationH = operations[symbol] {          
          switch operationH {
          case .Constant(let associatedValue): accumulator = assosicatedValue       
          case .UnaryOperation(let function): accumulator = fuction(accumulator)
          case .BinaryOperation(let function):         
                // 把第一个传入的参数和用户选中的运算符存放到了 pending 中
                // pending.binaryFunction 就是用户选中的运算方法,pending.firstOperand 就是第一次传入的参数了
                pending = PendingBinaryOperationInfo(binaryFunction: function, firstOperand: accumulator)    
                
          case .Equals:  
                // pending.binaryFunction 就是用户选中的运算方法,pending.firstOperand 就是第一次传入的参数了
                //  第一次传入的参数,和第二次传入的参数accumulator,在用户选中的方法下进行计算,将计算的结果赋值给accumulator      
                accumulator = pending!.binaryFunction(pending!.firstOperand, accumulator)        
          }
      }
  }

12. 完善 performOperation 方法之解决小bug

OK,还有几个小的bug要解决,首先,连续二元运算不能实现,其次,就是进行过第一次二元运算后,之后再进行计算,第一个数字永远是上次计算出的结果,而用户输入的,则成了第二个参数。这个效果还不太好!

解决方法如下:

    func performOperation(symbol: String) {      
      if let operationH = operations[symbol] {          
          switch operationH {
          case .Constant(let associatedValue): accumulator = assosicatedValue       
          case .UnaryOperation(let function): accumulator = fuction(accumulator)
          case .BinaryOperation(let function):   
                executePendingBinaryOpration()      
                pending = PendingBinaryOperationInfo(binaryFunction: function, firstOperand: accumulator)                 
          case .Equals:  
                executePendingBinaryOpration()     
          }
      }
  }

  private func executePendingBinaryOpration() {
      if pending != nil {
          accumulator = pending!.binaryFunction(pending!.firstOperand, accumulator)
          pending = nil
      }
  }

13. Stack View 优化界面显示,兼容横屏效果

每一行是一个Stack View,共五行Stack View(按钮多的话可以六行七行)
然后五行组合起来是一个Stack View
对 Stack View 的设置如下图:

第二课:Applying MVC_第1张图片

五行组合起来的再和Label是一个Stack View,属性设置如下:

三、课后作业

课后作业就是看课堂的PPT课件,然后做 Project 1 的编程项目。

好吧,Project 1的难度完全虐到我了,非常打击我。

必做项目

  1. Get the Calculator working as demonstrated in lectures 1 and 2.

  2. Your calculator already works with floating point numbers (e.g. if you touch 3 ÷ 4 =,it will properly show 0.75), however, there is no way for the user to enter a floating pointnumber directly. Fix this by allowing legal floating point numbers to be entered (e.g.“192.168.0.1” is not a legal floating point number!). You will have to add a new “.”button to your calculator. Don’t worry too much about precision or significant digitsin this assignment (including in the examples below).

  3. Add some more operations buttons to your calculator such that it has at least a dozenoperations total (it can have even more if you like). You can choose whateveroperations appeal to you. The buttons must arrange themselves nicely in portrait andlandscape modes on all iPhones.

  4. Use color to make your UI look nice. At the very least, your operations buttons mustbe a different color than your keypad buttons, but otherwise you can use color inwhatever way you think looks nice.

  5. Add a String property to your CalculatorBrain called description which returns adescription of the sequence of operands and operations that led to the value returnedby result. “=“ should never appear in this description, nor should “...”.

  6. Add a Bool property to your CalculatorBrain called isPartialResult which returnswhether there is a binary operation pending (if so, return true, if not, false).

  7. Use the two properties above to implement a UILabel in your UI which shows thesequence of operands and operations that led to what is showing in the display. IfisPartialResult, put . . . on the end of the UILabel, else put =. If theuserIsInTheMiddleOfTypingANumber, you can leave the UILabel showing whateverwas there before the user started typing the number. Examples ...

touching 7 + would show “7 + ...” (with 7 still in the display)
7 + 9 would show “7 + ...” (9 in the display)
7 + 9 = would show “7 + 9 =” (16 in the display)
7 + 9 = √ would show “√(7 + 9) =” (4 in the display)
7 + 9 √ would show “7 + √(9) ...” (3 in the display)
7 + 9 √ = would show “7 + √(9) =“ (10 in the display)
7 + 9 = + 6 + 3 = would show “7 + 9 + 6 + 3 =” (25 in the display)
7 + 9 = √ 6 + 3 = would show “6 + 3 =” (9 in the display)
5 + 6 = 7 3 would show “5 + 6 =” (73 in the display)
7 + = would show “7 + 7 =” (14 in the display)
4 × π = would show “4 × π =“ (12.5663706143592 in the display)
4 + 5 × 3 = would show “4 + 5 × 3 =” (27 in the display)
4 + 5 × 3 = could also show “(4 + 5) × 3 =” if you prefer (27 in the display)
  1. Add a C button that clears everything (your display, the new UILabel you addedabove, etc.). The Calculator should be in the same state as it is at application startupafter you touch this new button.

加分项目

  1. Implement a “backspace” button for the user to touch if they hit the wrong digitbutton. This is not intended to be “undo,” so if the user hits the wrong operationbutton, he or she is out of luck! It is up to you to decide how to handle the case wherethe user backspaces away the entire number they are in the middle of typing, buthaving the display go completely blank is probably not very user-friendly. You willfind the Strings and Characters section of the Swift Reference Guide to be veryhelpful here.

  2. Change the computed instance variable displayValue to be an Optional Doublerather than a Double. Its value should be nil if the contents of display.text cannotbe interpreted as a Double. Setting its value to nil should clear the display out. You’llhave to modify the code that uses displayValue accordingly.

  3. Figure out from the documentation how to use the NSNumberFormatter class to formatyour display so that it only shows 6 digits after the decimal point (instead of showingall digits that can be represented in a Double). This will eliminate the need forAutoshrink in your display. While you’re at it, make it so that numbers that areintegers don’t have an unnecessary “.0” attached to them (e.g. show “4” rather than“4.0” as the result of the square root of sixteen). You can do all this for yourdescription in the CalculatorBrain as well.

  4. Make one of your operation buttons be “generate a random number between 0 and1”. This operation button is not a constant (since it changes each time you invoke it).Nor is it a unary operation (since it does not operate on anything).

四、做完Project 1后的感想:人笨就要多努力

既然已经笨了(我认了),最好不要加上蠢,不然蠢笨加起来可没救了。(人可以笨,但不可以蠢)

既然这么笨,多看几遍视频好了,看个十遍八遍的,总能吸收点什么的,拿我来说,我这么笨,看到第五遍的时候,都写了这么长一篇笔记了。

反脆弱、反脆弱、、、

早晚我要完成这个 Project,虽然现在的水平还不行,我先记下,总有一天完成后,我要更新本文。

要是一年以后再看这篇文章,还是没有完成 Project,就尴尬了~

你可能感兴趣的:(第二课:Applying MVC)