第二課:Applying MVC

關(guān)于斯坦福的這個系列課程的所有筆記本缠,都在這個文集里:Developing Apps for iOS9

實(shí)際上課時間為:2016年3月30日斥扛。課程發(fā)布在iTunes上的時間是2016年4月23日,課程地址丹锹。

一稀颁、MVC 模式

MVC模式詳細(xì)的解釋,講了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當(dāng)然也可以把model的信息翻譯給view。不過model和view直接永遠(yuǎn)不說話租漂。

那么view能對controller說話嗎阶女?只能在某些情況下:action,outlet哩治,delegate秃踩,data source。

那么model能對controller說話嗎业筏?不能憔杨。那么model數(shù)據(jù)有了更新,怎么告訴controller數(shù)據(jù)有更新呢驾孔?通過使用 Notification&KVO 模式廣播數(shù)據(jù)更新的事件芍秆。

二惯疙、計算器Demo應(yīng)用

繼續(xù)代碼,增加功能妖啥。

1. 讓讀取寫入數(shù)據(jù)更容易的方法

在 viewController 中優(yōu)化一個方法:

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

2. 點(diǎn)擊運(yùn)算符的方法

計算器App上有數(shù)字Button霉颠,點(diǎn)擊數(shù)字創(chuàng)建Action連接方法是@IBAction private func touchDigit(sender: UIButton),有π荆虱、e蒿偎、加減乘除、等于這樣的運(yùn)算符怀读,當(dāng)點(diǎn)擊這些運(yùn)算符時诉位,觸發(fā)了@Action 方法:

    @IBAction private func performOperation(sender: UIButton) {
     //1. 把用戶輸入的數(shù)字傳給 Model
     //2. 把用戶點(diǎn)擊選中的運(yùn)算符(比如加減乘除中的某一個)傳給Model
     //3. 讀取把 Model 計算出來的結(jié)果,然后將結(jié)果顯示到App的label上
     //注意:這里都是和Model交互菜枷,因?yàn)镃ontroller只是翻譯苍糠,記得嗎,這些計算啤誊,實(shí)際上應(yīng)該是MVC中的M來做的事情
    }

3. 創(chuàng)建 Model

新建 CalculatorBrain.swift 文件岳瞭。
根據(jù)上面2中的3個注釋,我們可以知道至少需要三個東西:

  import Foundation
  class CalculatorBrain {

    //1. 把用戶輸入的數(shù)字傳給 Model
    func setOperand(operand: Double) { }

    //2. 把用戶點(diǎn)擊選中的運(yùn)算符(比如加減乘除中的某一個)傳給Model
    func performOperation(symbol: String) { }

    //3. (讀取把 Model 計算出來的結(jié)果蚊锹,然后將結(jié)果顯示到App的label上) = 也就是要有結(jié)果瞳筏,可讀的結(jié)果
    var result: Double {
        get {
             return 0.0   
        }
     }
  }

有了這三個方法,點(diǎn)擊某個運(yùn)算符后的方法就可以擴(kuò)充一下了:

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

            // 把用戶輸入的數(shù)字傳給 Model
            brain.setOperand(displayValue)
            // 正在輸入數(shù)字的狀態(tài)設(shè)為false
            userIsInTheMidlleOfTyping = false
        }
        
        if let mathematicalSymbol = sender.currentTitle {
            // 把用戶點(diǎn)擊選中的某個運(yùn)算符(比如加減乘除中的某一個)傳給Model
            brain.performOperation(mathematicalSymbol)           
        }
        // 讀取把 Model 計算出來的結(jié)果牡昆,然后將結(jié)果顯示到label里
        displayValue = brain.result
    }

4. API 知識

讓 View controller 中的屬性成為 private姚炕,在 view controller 文件里將變量和方法變成 private。

5. setOperand 方法 + 結(jié)果

點(diǎn)擊運(yùn)算符后已經(jīng)將數(shù)字傳給 Model 了丢烘,接下來在 Model 里實(shí)現(xiàn)具體的要求:

    // 創(chuàng)建局部變量柱宦,記錄用戶需要計算的數(shù)字
    private var accumulator = 0.0
    func setOperand(operand: Double) { 
        accumulator = operand
    }

最后的結(jié)果可以返回剛剛創(chuàng)建的局部變量了:

    var result: Double {
        get {
             return 0.0   
        }
     }

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

這個可是 CalculatorBrain 里最核心的部分了,進(jìn)行計算的地方铅协。那么不寫代碼捷沸,先理順一下思路。

     func performOperation(symbol: String) { }

首先要做的事情就是狐史,確認(rèn)傳過來的到底是哪個運(yùn)算符痒给。App上的運(yùn)算符除了數(shù)字,都可能是這個symbol骏全。那么可以用 switch 語句來切換苍柏。

確認(rèn)了是哪個運(yùn)算符之后就可以進(jìn)行計算了,π還好說姜贡,?這樣的也容易试吁,只需要一個數(shù)字就能計算了,加減乘除可怎么辦呢?這需要首先要記住傳入的第一個參數(shù)熄捍,然后記住運(yùn)算符是哪個烛恤,然后知道第二個運(yùn)算符,才能進(jìn)行計算的余耽。哦對了缚柏,別忘了還有等號這個按鈕。

把運(yùn)算符分分類發(fā)現(xiàn)碟贾,也就常量π币喧、一元運(yùn)算符cos、二元運(yùn)算符+袱耽、等號這四類了杀餐。那么我們可以用枚舉列出這四種情況:
swift private enum Operation { //常量 case Constant //一元運(yùn)算符 case UnaryOperation //二元運(yùn)算符 case BinaryOperation //等號 case Equals }

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

也就是說,任何一個運(yùn)算符都是這四個之一朱巨。每個運(yùn)算符都對應(yīng)著四個中的一個史翘,可以用詞典表示這樣的情況,是的冀续,詞典的值可以是枚舉恶座。

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

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

但是這樣還不能進(jìn)行計算。

8. 完善 performOperation 方法之 associated value

常量的話沥阳,就是用戶點(diǎn)擊π,傳入的數(shù)字就是π自点,不存在計算的問題桐罕。一元運(yùn)算符,傳入一個數(shù)字都就可以進(jìn)行計算了桂敛,二元運(yùn)算符需要進(jìn)行計算功炮,計算的方法必須要有兩個參數(shù),同時要有返回結(jié)果术唬,當(dāng)然薪伏,二元運(yùn)算符需要有個容器能記住第一次傳入的參數(shù)和用戶點(diǎn)擊的運(yùn)算符。

讓枚舉有 associated value粗仓。(optional也是枚舉嫁怀,optional有 associated value)在Swift里,function也可以成為associated value借浊。

  private enum Operation {
      // 如果是常量塘淑,只需Double類型的數(shù)值
      case Constant(Double)
      // 如果是一元運(yùn)算符,則需要一個方法蚂斤,這個方法獲知1個參數(shù)存捺,返回結(jié)果
      case UnaryOperation((Double) -> Double)
      // 如果是二元運(yùn)算符,需要一個方法曙蒸,這個方法是獲知2個數(shù)值捌治,返回結(jié)果
      case BinaryOperation((Double,Double) -> Double)
      case Equals
  }

9.完善 performOperation 方法之閉包Closure

枚舉這里改了岗钩,詞典那邊會出現(xiàn)閉包,如下:



利用閉包默認(rèn)參數(shù)$0$1可以省去一些代碼:

  private var operations: Dictionary<String,Operation> = [
      "cos" : Operation.UnaryOperation(cos),
      "sin" : Operation.UnaryOperation(sin),
      "tan" : Operation.UnaryOperation(tan),
      "±" : Operation.UnaryOperation({-$0}),
      "?" : Operation.UnaryOperation(cbrt),
      "√" : Operation.UnaryOperation(sqrt),
      "X2": Operation.UnaryOperation({$0*$0}),
      "X3": 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

再回到核心代碼上來肖油,這些枚舉兼吓、詞典什么,都是為了實(shí)現(xiàn) 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 方法之結(jié)構(gòu)體

二元運(yùn)算符的方法沒有寫,為啥呢疲恢?因?yàn)槲覀冞€沒有創(chuàng)建能夠存儲第一次傳入的值和選中的運(yùn)算符凶朗,那就創(chuàng)建一個容器存儲這兩個東西吧,我們用結(jié)構(gòu)體來存儲:

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

這樣就可以繼續(xù) performOperation 方法了(把第一個傳入的參數(shù)和用戶選中的運(yùn)算符存放到了 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):         
                // 把第一個傳入的參數(shù)和用戶選中的運(yùn)算符存放到了 pending 中
                // pending.binaryFunction 就是用戶選中的運(yùn)算方法显拳,pending.firstOperand 就是第一次傳入的參數(shù)了
                pending = PendingBinaryOperationInfo(binaryFunction: function, firstOperand: accumulator)    
                
          case .Equals:  
                // pending.binaryFunction 就是用戶選中的運(yùn)算方法棚愤,pending.firstOperand 就是第一次傳入的參數(shù)了
                //  第一次傳入的參數(shù),和第二次傳入的參數(shù)accumulator杂数,在用戶選中的方法下進(jìn)行計算宛畦,將計算的結(jié)果賦值給accumulator      
                accumulator = pending!.binaryFunction(pending!.firstOperand, accumulator)        
          }
      }
  }

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

OK,還有幾個小的bug要解決揍移,首先次和,連續(xù)二元運(yùn)算不能實(shí)現(xiàn),其次那伐,就是進(jìn)行過第一次二元運(yùn)算后踏施,之后再進(jìn)行計算,第一個數(shù)字永遠(yuǎn)是上次計算出的結(jié)果罕邀,而用戶輸入的畅形,則成了第二個參數(shù)。這個效果還不太好诉探!

解決方法如下:

    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 優(yōu)化界面顯示日熬,兼容橫屏效果

每一行是一個Stack View,共五行Stack View(按鈕多的話可以六行七行)
然后五行組合起來是一個Stack View
對 Stack View 的設(shè)置如下圖:


五行組合起來的再和Label是一個Stack View肾胯,屬性設(shè)置如下:

三竖席、課后作業(yè)

課后作業(yè)就是看課堂的PPT課件,然后做 Project 1 的編程項(xiàng)目阳液。

好吧怕敬,Project 1的難度完全虐到我了,非常打擊我帘皿。

必做項(xiàng)目

  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.

加分項(xiàng)目

  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).

四东跪、做完P(guān)roject 1后的感想:人笨就要多努力

既然已經(jīng)笨了(我認(rèn)了),最好不要加上蠢,不然蠢笨加起來可沒救了虽填。(人可以笨丁恭,但不可以蠢)

既然這么笨,多看幾遍視頻好了斋日,看個十遍八遍的牲览,總能吸收點(diǎn)什么的,拿我來說恶守,我這么笨第献,看到第五遍的時候,都寫了這么長一篇筆記了兔港。

反脆弱庸毫、反脆弱、衫樊、飒赃、

早晚我要完成這個 Project,雖然現(xiàn)在的水平還不行科侈,我先記下载佳,總有一天完成后,我要更新本文臀栈。

要是一年以后再看這篇文章蔫慧,還是沒有完成 Project,就尷尬了~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末权薯,一起剝皮案震驚了整個濱河市藕漱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崭闲,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件威蕉,死亡現(xiàn)場離奇詭異刁俭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)韧涨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門牍戚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人虑粥,你說我怎么就攤上這事如孝。” “怎么了娩贷?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵第晰,是天一觀的道長。 經(jīng)常有香客問我,道長茁瘦,這世上最難降的妖魔是什么品抽? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮甜熔,結(jié)果婚禮上圆恤,老公的妹妹穿的比我還像新娘。我一直安慰自己腔稀,他們只是感情好盆昙,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著焊虏,像睡著了一般淡喜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上炕淮,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天拆火,我揣著相機(jī)與錄音,去河邊找鬼涂圆。 笑死们镜,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的润歉。 我是一名探鬼主播模狭,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼踩衩!你這毒婦竟也來了嚼鹉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤驱富,失蹤者是張志新(化名)和其女友劉穎锚赤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體褐鸥,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡线脚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了叫榕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浑侥。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖晰绎,靈堂內(nèi)的尸體忽然破棺而出寓落,到底是詐尸還是另有隱情,我是刑警寧澤荞下,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布伶选,位于F島的核電站史飞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏考蕾。R本人自食惡果不足惜祸憋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肖卧。 院中可真熱鬧蚯窥,春花似錦、人聲如沸塞帐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽葵姥。三九已至荷鼠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間榔幸,已是汗流浹背允乐。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留削咆,地道東北人牍疏。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像拨齐,于是被迫代替她去往敵國和親鳞陨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容