본문 바로가기

iOS-Study

iOS Study : 11주차 - 계산기 클론 코딩

디자인

코드

ViewController.swift 파일 코드

import UIKit

var calculatorCount = 0

class ViewController: UIViewController {

    @IBOutlet private weak var display: UILabel!
    
    private var userIsInTheMiddleOfTyping: Bool = false
    
    //MVC가 생성될 때 한번만 호출
    override func viewDidLoad() {
        super.viewDidLoad()
        calculatorCount += 1
        print("Loa ded up a new Calculator (Count = \(calculatorCount))")
        //루트 대신 Z를 사용
        brain.addUnaryOperation(symbol: "Z") { [ unowned me = self ] in     //unoewd이기 때문에 me로 되어 있으면 힙에 아무것도 저장하지 않음
            me.display.textColor = UIColor.red
            return sqrt($0)
        }
    }
    
    //힙에서 사라지기 직전에 호출되는 특별한 메소드
    deinit {
          calculatorCount -= 1
        print("Calculator left the heap (Count = \(calculatorCount))")
    }
    
    @IBAction private func touchDigit(_ sender: UIButton) {
        let digit = sender.titleLabel?.text   //버튼의 현재 title
        //버튼을 누르게 되면 기존에 있던 0이 사라지고 버튼이 입력 됨
        if userIsInTheMiddleOfTyping {
            let textCurrentlyInDisplay = display.text!
            display.text = textCurrentlyInDisplay + digit!
        }
        else {
            display.text = digit
        }
        userIsInTheMiddleOfTyping = true
    }
    
    var displyValue : Double {
        get {
            return Double(display.text!)!
        }
        set {
            display.text = String(newValue)
        }
    }
    
    var savedProgram: CalculatorBrain.PropertyList?     //save버튼을 안눌렀을 수도 있기 때문에 Optional 타입
    
    //계산 값을 저장
    @IBAction func save() {
        savedProgram = brain.program
    }
    
    //저장한 값을 불러온다.
    @IBAction func restore() {
        if savedProgram != nil {
            brain.program = savedProgram!
            displyValue = brain.result
        }
    }
    
    
    //Model에 접근
    private var brain:CalculatorBrain = CalculatorBrain()
    
    @IBAction private func performOperation(_ sender: UIButton) {
        
        //만약 사용자가 숫자를 입력 중이라면
        if userIsInTheMiddleOfTyping {
            brain.setOperand(operand: displyValue)
            userIsInTheMiddleOfTyping = false
        }
        if let mathmaticalSymbol = sender.titleLabel?.text {
            brain.performOperation(symbol: mathmaticalSymbol)
        }
        displyValue = brain.result
    }
}

 

Model을 담당하는 CalculatorBrain.swift 파일 코드

//Model
import Foundation

class CalculatorBrain {
    
    //결과를 누적해 나가기 위한 변수
    private var accumulator:Double = 0.0
    private var internerProgram = [Any]()
    
    //피연산숫자
    func setOperand(operand: Double) {
        accumulator = operand   //피연산숫자로 들어오는 값으로 accumulator를 set
        internerProgram.append(operand)
    }
    
    func addUnaryOperation(symbol: String, operation: @escaping (Double) -> Double) {
        operations[symbol] = Operation.UnaryOperaion(operation)
    }
    
    var operations: Dictionary<String,Operation> = [
        "π" : Operation.Constant(.pi),
        "e" : Operation.Constant(M_E),
        "±" : Operation.UnaryOperaion({ -$0 }),
        "√" : Operation.UnaryOperaion(sqrt),
        "cos" : Operation.UnaryOperaion(cos),
        "×" : Operation.BinaryOperation({ $0 * $1 }),   //클로저 사용
        "÷" : Operation.BinaryOperation({ $0 / $1 }),
        "+" : Operation.BinaryOperation({ $0 + $1 }),
        "-" : Operation.BinaryOperation({ $0 - $1 }),
        "=" : Operation.Equals
    ]
    
    enum Operation {
        case Constant(Double)   //상수, () 안에 자료형을 넣어서 Dictionary에서 사용
        case UnaryOperaion((Double) -> Double) //단항 연산, 함수를 사용하기 위해 자료형과 반환값의 자료형을 넣어서 사용
        case BinaryOperation((Double, Double) -> Double)    //이항 연산, 두개의 Double형 자료를 입력 받아 Double형 결과를 반환
        case Equals
    }
    
    func performOperation(symbol: String) {
        internerProgram.append(symbol)
        if let operation = operations[symbol] {
            switch operation {
            case .Constant(let associatedConstantValue):
                accumulator = associatedConstantValue  //값을 가져오기 위해 지역변수 선언
                
            //Double형을 입력 받아 Double형을 반환하기 때문에 함수처럼 사용
            case .UnaryOperaion(let function):
                accumulator = function(accumulator)
            case .BinaryOperation(let function):
                excutePendingBinaryOperation()
                pending = PendingBinaryOperationInfo(binaryFunction: function, firstOperand: accumulator)
            case .Equals: excutePendingBinaryOperation()
            }
        }
    }
    
    private func excutePendingBinaryOperation() {
        if pending != nil {
            accumulator = pending!.binaryFunction(pending!.firstOperand, accumulator)
            pending = nil
        }
    }
    
    private var pending: PendingBinaryOperationInfo?
    
    //struct(구조체)는 enum처럼 값으로 전달된다. 반면에 클래스는 참조형식으로 전달된다.
    //참조형식의 경우 heap영역에 저장되었다가 전달하게 되면 값 그 자체가 아닌 메모리의 주소를 전달하게 된다.
    //대기중인 이항연산 정보 - 이항연산에 필요한 값 2개를 가지고 있음
    private struct PendingBinaryOperationInfo {
        var binaryFunction: (Double, Double) -> Double
        var firstOperand: Double
    }
    
    typealias PropertyList = AnyObject   //type를 만들 수 있게 해준다.
    
    //program이 AnyObject타입과 동시에 PropertyList이기도 함
    var program: PropertyList {
        get {
            return internerProgram as AnyObject
        }
        set {
            clear()
            if let arrayOfOps = newValue as? [AnyObject] {
                for op in arrayOfOps {
                    if let operand = op as? Double {
                        setOperand(operand: operand)
                    }
                    else if let operation = op as? String {
                         performOperation(symbol: operation)
                    }
                }
            }
        }
    }
    
    func clear() {
        accumulator = 0.0
        pending = nil
        internerProgram.removeAll()
    }
    
    var result: Double {
        get {
            return accumulator
        }
    }
}

 

실행 화면