Swift項目實戰(zhàn)-簡單計算器

簡介

斯坦福cs193p 2015年的冬季iOS8課程已經(jīng)陸續(xù)上線了。說實話當我看到它上線的時候還是挺激動的台汇,畢竟我也是從iOS6看到iOS7,每次都從那位 Paul Hegarty 做的demo中學到不少知識(哪怕抄一篇也是練手捌蚯伞)洋访。如果說喬布斯的本事是將復雜精密的電腦科技包裝成一個簡單美觀且易用的iPhone并介紹給大眾消費者的話,那么Paul老爺子的本事則是把復雜的iOS app開發(fā)通過他的課程和demo講解的淺顯易懂斑司,幫助任何愿意成為iOS開發(fā)者的朋友能更好的熟悉開發(fā)和上手渗饮。引用他的話就是:一次demo演示勝過千言萬語。但是前提是:首先要自己熟悉面向?qū)ο蟮母拍睿煜ゎ惡屠^承等等互站,否則越往后聽就會越跟不上私蕾。

今年的iOS課程最大的亮點就是swift,就目前發(fā)布的視頻來看胡桃,感覺斯坦福cs193p從今年起已經(jīng)不再該課程中過多的介紹objective c了踩叭,所有的demo全部體現(xiàn)的是嶄新的語言和思維。作為cs193p的粉絲翠胰,我也在這里發(fā)布自己的課后作業(yè)和解析容贝,如有任何疑問或反饋,歡迎溝通之景。(相比較德國的rwth aachen大學斤富,在今年推出的iOS8開發(fā)課程中僅有兩節(jié)swift視頻,如果有興趣了解以objective c為基礎(chǔ)的cocoa touch的朋友锻狗,可以從iTunes U下載他們的課程满力,很有幫助。)

概述

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var display: UILabel!

    // 用戶是不是已經(jīng)輸入
    var userIsInTheMiddleOfTypingANumber:Bool = false

    @IBAction func appendDigit(sender: UIButton) {
        // Swift是非常強類型的語言
        // Swift有一個非常強大的特性,叫做類型推導
        let digit = sender.currentTitle!
        
        if userIsInTheMiddleOfTypingANumber {
            // 可選值(Optional)是不可以拼接字符串的,所以在這個地方,需要進行解包
            display.text = display.text! + digit
        } else {
            display.text = digit
            userIsInTheMiddleOfTypingANumber = true
        }
        print("digit = \(digit)")
    }
}

Tips

  • let用來定義常量(const)屋谭,常量定義之后就不可以改變脚囊。一定要初始化,不初始化會報錯桐磁。
  • var用來定義變量悔耘,一定要初始化,不初始化會報錯我擂。
  • swift語言有類型推導衬以,定義的變量或者常量的類型通過右邊的值來決定。
  • ()用來在字符串中包含 相應的變量或者常量
  • optional Type:這種類型的值有兩種狀態(tài)校摩,一種是無值(nil)看峻,一種是有值。
    也就是衙吩,Optional類型允許變量沒有值互妓,其它類型如果沒有初始化值在使用時會報錯 。
    optional 在有值得時候坤塞,值的類型是由 冯勉?左邊的值得類型來決定∧≤剑可以通過灼狰!來解包取得相應的類型。
    optional類型的值不可以用于右值浮禾!
  • 按住option鍵點擊相應的變量交胚,來查看文檔份汗。

完成計算器,初探MVC設(shè)計模式

回車鍵及相關(guān)的代碼

var operandStack = [Double]()
    @IBAction func enter() {
        userIsInTheMiddleOfTypingANumber = false
        operandStack.append(displayValue)
        print("operandStack = \(operandStack)")
    }
  • 這個變量是一個內(nèi)部的棧,去存儲你輸入的這些數(shù),他的類型是一個數(shù)組,這個數(shù)組里存放的是Double的變量,注意在這個地方要初始化.(我們不能使用沒有經(jīng)過初始化的,會報錯)
  • var operandStack: Array<Double> = Array<Double>()
    var operandStack = Array<Double>()
// 用來進棧的數(shù)據(jù)
    var displayValue:Double {
        get {
            // 將字符串轉(zhuǎn)換為double
            return NSNumberFormatter().numberFromString(display.text!)!.doubleValue
        }
        set {
            // 將value轉(zhuǎn)換成字符串
            display.text = "\(newValue)"
            // 設(shè)置重新開始輸入字符串
            userIsInTheMiddleOfTypingANumber = false
        }
    }

其中NSNumberFormatter類中比較常用的兩個方法:

  • func stringFromNumber(number:NSNumber) -> String?
    NSNumber 轉(zhuǎn)換成String
  • func numberFromString(string:String) -> NSNumber?
    String轉(zhuǎn)換成 NSNumber
    NSNumberFormatter().numberFromString(display.text!)!.doubleValue將字符串轉(zhuǎn)換為NSnumber蝴簇,然后將NSNumber轉(zhuǎn)換為Double杯活。
    NSNumberFormatter()是用來初始化實例用的,這里是隱式军熏,表示用對象來引用其中的方法轩猩。

加減乘除開根號運算

v0.7

    @IBAction func operate(sender: UIButton) {
        let operation = sender.currentTitle!
        
        if userIsInTheMiddleOfTypingANumber
        {
            enter()
        }
        
        switch operation {
        case "×":
            if operandStack.count >= 2 {
                displayValue = operandStack.removeLast() * operandStack.removeLast()
                enter()
            }
        case "÷":
            break
        case "+":
            break
        case "?":
            break
        default:
            break
        }
}

v0.8

    @IBAction func operate(sender: UIButton) {
        let operation = sender.currentTitle!
        
        if userIsInTheMiddleOfTypingANumber
        {
            enter()
        }
        
        switch operation {
        case "×": performOperation(multiply)
        case "÷":
            break
        case "+":
            break
        case "?":
            break
        default:
            break
        }
   }

    // 定義一個方法用來進行加減乘除運算,參數(shù)類型是一個方法:(Double, Double)->Double
    func performOperation(operation:(Double,Double) -> Double) {
        if operandStack.count >= 2 {  // 棧中必須有兩個元素才能進行加減乘除的運算
            
            // 把最后的兩個元素分別出棧,然后進行運算
            displayValue = operandStack.removeLast() * operandStack.removeLast()
            enter()
        }
    }
    
    func multiply(op1:Double, op2:Double) -> Double {
        return op1 * op2
    }
  • Swift中可以將一個方法作為另一個方法的參數(shù):
    performOperation(multiply)是將multiply方法作為performOperation方法的參數(shù)

v0.9

    @IBAction func operate(sender: UIButton) {
        let operation = sender.currentTitle!
        
        if userIsInTheMiddleOfTypingANumber
        {
            enter()
        }
        
        switch operation {
        case "×": performOperation({ (op1:Double, op2:Double) -> Double in
            return op1 * op2
        })
        case "÷":
            break
        case "+":
            break
        case "?":
            break
        default:
            break
        }
    }
    
    // 定義一個方法用來進行加減乘除運算,參數(shù)類型是一個方法:(Double, Double)->Double
    func performOperation(operation:(Double,Double) -> Double) {
        if operandStack.count >= 2 {  // 棧中必須有兩個元素才能進行加減乘除的運算
            
            // 把最后的兩個元素分別出棧,然后進行運算
            displayValue = operandStack.removeLast() * operandStack.removeLast()
            enter()
        }
    }
  • Swift中可以隱式的將一個方法作為另一個方法的參數(shù)
case "×": performOperation({ (op1:Double, op2:Double) -> Double in
            return op1 * op2
        })

可以看到隱式方法作為參數(shù)的時候,要放在{}里面荡澎,稱之為閉包

  • Swift有類型自動推導的功能均践,所以可以簡寫為:
case "×": performOperation({ (op1,op2) in
            return op1 * op2
        })
performOperation({ (op1,op2) in return op1 * op2 })
performOperation({ (op1,op2) in op1 * op2 })
  • Swift中編譯器會自動給方法中的參數(shù)傳一個默認值,第一個參數(shù)$0,第二個參數(shù)$1........
    所以可以繼續(xù)簡化為:
performOperation({ $0 * $1 })
  • Swift中如果一個方法是另外一個方法的最后一個參數(shù),那么可以將此方法放到原方法后面:
performOperation() { $0 * $1 }
  • 方法中無參數(shù)了可以直接去掉()
performOperation { $0 * $1 }
@IBAction func operate(sender: UIButton) {
        let operation = sender.currentTitle!
        
        if userIsInTheMiddleOfTypingANumber
        {
            enter()
        }
        
        switch operation {
        case "×": performOperation { $0 * $1 }
        case "÷": performOperation { $1 / $0 }  // 除數(shù)是棧中最先彈出的元素
        case "+": performOperation { $0 + $1 }
        case "?": performOperation { $1 - $0 }
        default: break
        }
    }
    
    // 定義一個方法用來進行加減乘除運算,參數(shù)類型是一個方法:(Double, Double)->Double
    func performOperation(operation:(Double,Double) -> Double) {
        if operandStack.count >= 2 {  // 棧中必須有兩個元素才能進行加減乘除的運算
            
            // 把最后的兩個元素分別出棧,然后進行運算
            displayValue = operation(operandStack.removeLast(),operandStack.removeLast())
            enter()
        }
    }

v1.0

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var display: UILabel!
    

    var userIsInTheMiddleOfTypingANumber:Bool = false

    @IBAction func appendDigit(sender: UIButton) {
        let digit = sender.currentTitle!
        
        if userIsInTheMiddleOfTypingANumber {
            display.text = display.text! + digit
        } else {
            display.text = digit
            userIsInTheMiddleOfTypingANumber = true
        }
        print("digit = \(digit)")
    }
    
    @IBAction func operate(sender: UIButton) {
        let operation = sender.currentTitle!
        
        if userIsInTheMiddleOfTypingANumber
        {
            enter()
        }
        
        switch operation {
        case "×": performOperation { $0 * $1 }
        case "÷": performOperation { $1 / $0 }
        case "+": performOperation { $0 + $1 }
        case "?": performOperation { $1 - $0 }
        case "√": performOperation { sqrt($0)}
        default: break
        }


    }
    
    // 定義一個方法用來進行加減乘除運算,參數(shù)類型是一個方法:(Double, Double)->Double
    func performOperation(operation:(Double,Double) -> Double) {
        if operandStack.count >= 2 {  // 棧中必須有兩個元素才能進行加減乘除的運算
            // 把最后的兩個元素分別出棧,然后進行運算
            displayValue = operation(operandStack.removeLast(),operandStack.removeLast())
            enter()
        }
    }
    
    // 添加private,不讓Swift編譯器將其暴露給Objective-C運行時
    private func performOperation(operation:Double -> Double)  {
        // 棧中必須多于一個元素才能進行開平方
        if operandStack.count >= 1 {
            displayValue = operation(operandStack.removeLast())
            enter()
        }
    }
    
    var operandStack = [Double]()
    
    @IBAction func enter() {
        userIsInTheMiddleOfTypingANumber = false
        
        operandStack.append(displayValue)
        
        print("operandStack = \(operandStack)")
        

    }
    
    // 用來進棧的數(shù)據(jù)
    var displayValue:Double {
        get {
            // 將字符串轉(zhuǎn)換為double
            return NSNumberFormatter().numberFromString(display.text!)!.doubleValue
        }
        set {
            // 將value轉(zhuǎn)換成字符串
            display.text = "\(newValue)"
            // 設(shè)置重新開始輸入字符串
            userIsInTheMiddleOfTypingANumber = false
        }
    }
    
}

MVC設(shè)計模式初探

MVC設(shè)計模式

計算器的Model層

定義了一個私有的enum

 private enum Op {
        case Operand(Double)                                  // 操作數(shù)
        case UnaryOperation(String,Double -> Double)          // 一元運算
        case BinaryOperation(String,(Double,Double) ->Double) // 二元運算
}

聲明了一個數(shù)組opStack和一個字典knownOps并初始化.

private var opStack = [Op]()
private var knownOps = [String:Op]()
 var knownOps = Dictionary<String,Op>() 

簡寫為

 private var knownOps = [String:Op]()

函數(shù)的初始化

init() {
        knownOps["×"] = Op.BinaryOperation("×", *)
        knownOps["÷"] = Op.BinaryOperation("÷"){ $1 / $0}
        knownOps["+"] = Op.BinaryOperation("+", +)
        knownOps["?"] = Op.BinaryOperation("?"){ $1 - $0}
        knownOps["√"] = Op.UnaryOperation("√",sqrt)
    }

函數(shù)的初始化,為 knownOps 字典賦值,這里比較有趣,BinaryOperation 在定義需要的參數(shù)是一個 String 和函數(shù)(這個函數(shù)需要 2 個 Double 類型的參數(shù)摩幔,返回值是 Double 類型), 而在初始化時這樣寫 knownOps["÷"] = Op.BinaryOperation("÷", { $1 / $0 }) 并不奇怪,有趣的是這里的閉包竟然可以直接省略成 * 和 + 就可以代表 (Double, Double) -> Double 了.這是因為在 Swift 里所有的操作符都是符號,* 其實就是代表 { $0 * $1 } 了,+ 代表 { $0 + $1 } ,所以可以直接省略寫成 * 和 + ,至于 ÷ 和 ? 后面的閉包 { $1 / $0 } 和 { $1 - $0 } 不能省略成 ÷ 和 ? ,因為運算方向是相反的彤委。

knownOps["×"] = Op.BinaryOperation("×"){ $0 * $1} 簡寫為
knownOps["×"] = Op.BinaryOperation("×", *)

knownOps["+"] = Op.BinaryOperation("+"){ $0 + $1} 簡寫為
knownOps["+"] = Op.BinaryOperation("+", +)

knownOps["√"] = Op.UnaryOperation("√"){ sqrt($0)} 簡寫為
knownOps["√"] = Op.UnaryOperation("√",sqrt)

evaluate

// 函數(shù)返回的是一個元組(Tuple),返回一個Tuple類型,里面有rusult和remainingOps
    private func evaluate(ops: [Op]) -> (result: Double?,remainingOps: [Op]) {
        if !ops.isEmpty {
            
            // 這里的 let op = ops.removeLast() 會報錯,因為 ops 是一個不可變變量.
            // 為什么是一個不可變變量呢?swift 里規(guī)定,傳過來的參數(shù),除了類之外其他的都是值拷貝,類為引用.
            // 數(shù)組和字典都是結(jié)構(gòu)體,所以是值拷貝,這點需要牢記或衡。
            var remainingOps = ops
            let op = remainingOps.removeLast()
            
            switch op {
            case .Operand(let operand):
                return (operand,remainingOps)
            case .UnaryOperation(_, let operation):
                let operandEvaluation = evaluate(remainingOps) // 這樣我就遞歸了
                if let operand = operandEvaluation.result {
                    return (operation(operand),operandEvaluation.remainingOps)
                }
            case .BinaryOperation(_, let operation):
                let op1Evalution = evaluate(remainingOps)
                if let operand1 = op1Evalution.result {
                    let op2Evalution = evaluate(op1Evalution.remainingOps)
                    if let operand2 = op2Evalution.result {
                        return (operation(operand1,operand2),op2Evalution.remainingOps)
                    }
                }
            }
        }
        return (nil,ops)
    }

遞歸計算棧示例

×
4

5
6
計算過程:4×(5+6)
我要再次遞歸,用于獲取做加法的操作數(shù)
我得到5不錯,我還要再次遞歸,得到一個6焦影,填寫到這里,這樣,我計算完整個棧
就這樣,你看到我這樣通過來回遞歸,獲取操作符所需要的操作數(shù)
一旦我取得的是操作數(shù),本次遞歸就可以停止

CalculatorBrain.swift

import Foundation

// 作為計算器的Model層,這里將類命名為CalculatorBrain
class CalculatorBrain
{
    private enum Op {
        case Operand(Double)                                  // 操作數(shù)
        case UnaryOperation(String,Double -> Double)          // 一元運算
        case BinaryOperation(String,(Double,Double) ->Double) // 二元運算
        
        var desciption: String {
            get {
                switch self {
                case .Operand(let operand):
                    return "\(operand)"
                case .UnaryOperation(let symbol, _):
                    return symbol
                case .BinaryOperation(let symbol, _):
                    return symbol
                }
                
            }
        }
    }
    private var opStack = [Op]()
    
    private var knownOps = [String:Op]()

    init() {
        knownOps["×"] = Op.BinaryOperation("×", *)
        knownOps["÷"] = Op.BinaryOperation("÷"){ $1 / $0}
        knownOps["+"] = Op.BinaryOperation("+", +)
        knownOps["?"] = Op.BinaryOperation("?"){ $1 - $0}
        knownOps["√"] = Op.UnaryOperation("√",sqrt)
    }
    
    private func evaluate(ops: [Op]) -> (result: Double?,remainingOps: [Op]) {
        if !ops.isEmpty {
            var remainingOps = ops
            let op = remainingOps.removeLast()
            
            switch op {
            case .Operand(let operand):
                return (operand,remainingOps)
            case .UnaryOperation(_, let operation):
                let operandEvaluation = evaluate(remainingOps) // 這樣我就遞歸了
                if let operand = operandEvaluation.result {
                    return (operation(operand),operandEvaluation.remainingOps)
                }
            case .BinaryOperation(_, let operation):
                let op1Evalution = evaluate(remainingOps)
                if let operand1 = op1Evalution.result {
                    let op2Evalution = evaluate(op1Evalution.remainingOps)
                    if let operand2 = op2Evalution.result {
                        return (operation(operand1,operand2),op2Evalution.remainingOps)
                    }
                }
            }
        }
        return (nil,ops)
    }
    
    func evaluate() -> Double? {
        // 令一個Tuple等于函數(shù)的返回值,_起占位作用,表示我不關(guān)心該參數(shù)。
        let (result,remainder) = evaluate(opStack)
        // opStack結(jié)果加上剩下的remainder
        print("\(opStack) = \(result) with \(remainder) left over")
        return result
    }
    
    func pushOperand(operand:Double) ->Double? {
        opStack.append(Op.Operand(operand))
        return evaluate()
    }
    
    func performOperation(symbol:String) -> Double? {
        if let operation = knownOps[symbol] {
            opStack.append(operation)
        }
        return evaluate()
    }

    
}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var display: UILabel!
    
    var brain = CalculatorBrain()

    var userIsInTheMiddleOfTypingANumber:Bool = false

    @IBAction func appendDigit(sender: UIButton) {
        let digit = sender.currentTitle!
        
        if userIsInTheMiddleOfTypingANumber {
            display.text = display.text! + digit
        } else {
            display.text = digit
            userIsInTheMiddleOfTypingANumber = true
        }
        print("digit = \(digit)")
    }
    
    @IBAction func operate(sender: UIButton) {
        
        if userIsInTheMiddleOfTypingANumber
        {
            enter()
        }
        

        if let operation = sender.currentTitle {
            if let result = brain.performOperation(operation){
                displayValue = result
            } else {
                displayValue = 0
            }
            
        }

    }
    
    @IBAction func enter() {
        userIsInTheMiddleOfTypingANumber = false

        if let result = brain.pushOperand(displayValue){
            displayValue = result
        } else {
            displayValue = 0
        }
    }
    
    // 用來進棧的數(shù)據(jù)
    var displayValue:Double {
        get {
            // 將字符串轉(zhuǎn)換為double
            return NSNumberFormatter().numberFromString(display.text!)!.doubleValue
        }
        set {
            // 將value轉(zhuǎn)換成字符串
            display.text = "\(newValue)"
            // 設(shè)置重新開始輸入字符串
            userIsInTheMiddleOfTypingANumber = false
        }
    }
    
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末封断,一起剝皮案震驚了整個濱河市斯辰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坡疼,老刑警劉巖彬呻,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異柄瑰,居然都是意外死亡闸氮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門教沾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蒲跨,“玉大人,你說我怎么就攤上這事授翻』虮” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵堪唐,是天一觀的道長隆箩。 經(jīng)常有香客問我,道長羔杨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任杨蛋,我火速辦了婚禮兜材,結(jié)果婚禮上理澎,老公的妹妹穿的比我還像新娘。我一直安慰自己曙寡,他們只是感情好糠爬,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著举庶,像睡著了一般执隧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上户侥,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天镀琉,我揣著相機與錄音,去河邊找鬼蕊唐。 笑死屋摔,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的替梨。 我是一名探鬼主播钓试,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼副瀑!你這毒婦竟也來了弓熏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤糠睡,失蹤者是張志新(化名)和其女友劉穎挽鞠,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铜幽,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡滞谢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了除抛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狮杨。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖到忽,靈堂內(nèi)的尸體忽然破棺而出橄教,到底是詐尸還是另有隱情,我是刑警寧澤喘漏,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布护蝶,位于F島的核電站,受9級特大地震影響翩迈,放射性物質(zhì)發(fā)生泄漏持灰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一负饲、第九天 我趴在偏房一處隱蔽的房頂上張望堤魁。 院中可真熱鬧喂链,春花似錦、人聲如沸妥泉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盲链。三九已至蝇率,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刽沾,已是汗流浹背本慕。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留悠轩,地道東北人间狂。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像火架,于是被迫代替她去往敵國和親鉴象。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

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