Swift 프로그래밍 입문 5

26강 프로토콜

  • 특정 역할을 수행하기 위한 메서드, 프로퍼티, 이니셜라이저 등 요구사항을 미리 정의
  • 특정 프로토콜을 채택하는 경우 실제로 구현한 것을 준수한다고 표현
    protocol  Talkable {
        var topic: String { get set }
        var  language: String { get }
        func talk()
        init(topic: String, language: String)
    }
    
    struct Person:  Talkable {
        //var topic: String
        // 읽고쓰기 전용인 경우 let은 안됨
        //let language: String
        
        // 읽기전용 프로퍼티 요구는 연산 프로퍼티로 대체 가능
        //    var language: String { return "한국어" }
        //    var subject: String = ""
        //    var topic: String {
        //        set {
        //            self.subject = newValue
        //        }
        //        get {
        //            return self.subject
        //        }
        //    }
        
        func talk() {
            print("\(topic)에 대해 \(language)로 말합니다")
        }
        
        init(topic: String, language: String) {
            self.topic = topic
            self.language = language
        }
    }
    
    // 프로토콜은 다중 상속이 가능
    protocol Readable {
        func read()
    }
    protocol Writeable {
        func write()
    }
    protocol ReadSpeakable: Readable {
        func speak()
    }
    protocol ReadWriteSpeakalbe: Readable, Writeable {
        func speak()
    }
    
    //  클래스 상속과 프로토콜
    class SuperClass: Readable {
        func read() {
            print("read")
        }
    }
    // 순서를 꼭 지켜야함 - 상속받는 클래스가 가장 먼저
    class SubClass: SuperClass, Writeable, ReadSpeakable {
        func write() {
            print("wirte")
        }
        func speak() {
            print("speak")
        }
    }
    
    let sup: SuperClass = SuperClass()
    var  someAny: Any = sup
    someAny is Readable
    someAny is Writeable

27강 익스텐션

  • 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가 할 수 있는 기능
  • 코드를 몰라도 타입만 알고 있다면 그 타입의 기능을 확장 가능
  • 익스텐션이 타입에 추가 할 수 있는 기능 → 익스텐션은 타입에 새로운 기능을 추가할 수는 있지만, 기존에 존재하는 기능을 재정의 할 수 없음.
    • 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
    • 타입 메서드 / 인스턴스 메서드
    • 이니셜라이저
    • 서브스크립트
    • 중첩 타입
    • 특정 프로토콜을 준수할 수 있도록 기능 추가
  • 클래스 상속과 익스텐션 비교
  • 외부 라이브러리나 프레임워크를 가져다 썼다면 원본 소스를 수정하지 못함. 이처럼 외부에서 가져온 타입에 내가 원하는 기능을 추가하고자 할 때 익스텐션을 사용 → 따로 상속을 받지 않아도 되며, 구조체와 열거형에도 기능을 추가할 수 있으므로 익스텐션은 매우 편리한 기능
  • 프로토콜 중심 프로그래밍 참고
    extension Int {
        var isEven: Bool {
            return self % 2 == 0
        }
        var isOdd: Bool {
            return self % 2 == 1
        }
    		func multiply(by n: Int) -> Int {
            return self * n
        }
    }
    print(1.isEven) // false
    print(2.isEven) // true
    
    var number: Int = 3
    print(number.isEven) // false
    print(number.isOdd) // true
    
    print(3.multiply(by: 2))  // 6

- 이니셜라이져
    - 편의 이니셜라이저는 추가 가능하나, 지정 이니셜라이저는 추가할  없음. 지정 이니셜라이저와 디이니셜라이저는 반드시 클래스 타입의 구현부에 위치

    extension String {
        init(int: Int) {
            self = "\(int)"
        }
        
        init(double: Double) {
            self = "\(double)"
        }
    }
    
    let stringFromInt: String = String(int: 100) 
    // "100"

28강 오류 처리

  • Error 라는 프로토콜을 사용 → 사실상 요구사항이 없는 빈 프로토콜
  • 열거형의 연관 값: 연관값은 열거 내의 하나의 사례에 대해 각 사례 인스턴스마다 다른 값들을 가지게 하고 싶을 때 사용 참고
    enum VendingMachineError: Error {
        case invalidInput
        case insufficientFunds(moneyNeeded: Int) // 오류에 대한 부가정보를 제공
        case outOfStock
    }

- 함수에서 발생한 오류

    class VendingMachine {
        let itemPrice: Int = 100
        var itemCount: Int = 5
        var deposited: Int = 0
        
        // 돈 받기 메서드
        func receiveMoney(_ money: Int) throws {
            
            // 입력한 돈이 0이하면 오류를 던집니다
            guard money > 0 else {
                throw VendingMachineError.invalidInput
            }
            
            // 오류가 없으면 정상처리를 합니다
            self.deposited += money
            print("\(money)원 받음")
        }
        
        // 물건 팔기 메서드
        func vend(numberOfItems numberOfItemsToVend: Int) throws -> String {
            
            // 원하는 아이템의 수량이 잘못 입력되었으면 오류를 던집니다
            guard numberOfItemsToVend > 0 else {
                throw VendingMachineError.invalidInput
            }
            
            // 구매하려는 수량보다 미리 넣어둔 돈이 적으면 오류를 던집니다
            guard numberOfItemsToVend * itemPrice <= deposited else {
                let moneyNeeded: Int
                moneyNeeded = numberOfItemsToVend * itemPrice - deposited
                
                throw VendingMachineError.insufficientFunds(moneyNeeded: moneyNeeded)
            }
            
            // 구매하려는 수량보다 요구하는 수량이 많으면 오류를 던집니다
            guard itemCount >= numberOfItemsToVend else {
                throw VendingMachineError.outOfStock
            }
            
            // 오류가 없으면 정상처리를 합니다
            let totalPrice = numberOfItemsToVend * itemPrice
            
            self.deposited -= totalPrice
            self.itemCount -= numberOfItemsToVend
            
            return "\(numberOfItemsToVend)개 제공함"
        }
    }
  • do - catch
    do {
        try machine.receiveMoney(0)
    } catch VendingMachineError.invalidInput {
        print("입력이 잘못되었습니다")
    } catch VendingMachineError.insufficientFunds(let moneyNeeded) {
        print("\(moneyNeeded)원이 부족합니다")
    } catch VendingMachineError.outOfStock {
        print("수량이 부족합니다")
    } // 입력이 잘못되었습니다
    
    do {
        try machine.receiveMoney(300)
    } catch /*(let error)*/ {
        switch error {
        case VendingMachineError.invalidInput:
            print("입력이 잘못되었습니다")
        case VendingMachineError.insufficientFunds(let moneyNeeded):
            print("\(moneyNeeded)원이 부족합니다")
        case VendingMachineError.outOfStock:
            print("수량이 부족합니다")
        default:
            print("알수없는 오류 \(error)")
        }
    }
    
    do {
        result = try machine.receiveMoney(0)
    } 
    // do만 pass
  • try? try!
    • try? 정상적으로 처리되면 오류처리결과를 받지 않고, 오류가 발생하면 nil
    • try! 오류가 발생하지 않을것이라는 강력한 확신 가질때 사용 만약 오류가 발생하면 런타임 오류 발생
    result = try? machine.vend(numberOfItems: 2)
    result // nil
    
    result = try! machine.vend(numberOfItems: 1)
    result // 1개 제공함
    result = try! machine.vend(numberOfItems: 1) // 런타임 오류 발생!
  • rethrows
    • call_back
        enum MyError: Error {
            case cannotDivide
        }
        
        func divideNumber(first: Float, second: Float) throws -> Float {
            if second == 0 {
                throw MyError.cannotDivide
            }
            return first/second
        }
        
        func calculateFunction(function: (Float, Float) throws -> Float) rethrows {
            print(try function(2, 0))
        }
        
        do {
            try calculateFunction(function: divideNumber)
        } catch let error as MyError {
            switch error {
            case .cannotDivide:
                 print("0으로 나누었다.")
            }
        }
        
        func calculateFunction(function: (Float, Float) throws -> Float) {
            do {
                print(try function(2, 0))
            } catch let error as MyError {
          	      // Error Handling..
            }
        }
  • defer
  • 코드블럭에서 벗어날 때 defer 구문 안의 코드 블럭이 실행되는 특징, 에러가 있든, 없든 무조건 실행이 보장
    func processFile(filename: String) throws {
        if exists(filename) {
            let file = open(filename)
            defer {
                close(file)
            }
            while let line = try file.readline() {
                // Work with the file.
            }
            // close(file) is called here, at the end of the scope.
        }
    }

    //파일을 정상적으로 닫아주어 메모리에서 해제해줘야 하기 때문에 defer 문을 통해 파일을 닫아줌

29강 고차함수

  • 다른 함수를 전달인자로 받거나 함수실행의 결과를 함수로 반환하는 함수
  • map
    let numbers: [Int] = [0, 1, 2, 3, 4]
    var doubledNumbers: [Int]
    var strings: [String]
    
    for number in numbers {
        doubledNumbers.append(number * 2)
        strings.append("\(number)")
    }
    
    print(doubledNumbers) // [0, 2, 4, 6, 8]
    print(strings) // ["0", "1", "2", "3", "4"]
    
    doubledNumbers = numbers.map({ (number: Int) -> Int in
        return number * 2
    })
    // 매개변수, 반환 타입, 반환 키워드(return) 생략, 후행 클로저
    doubledNumbers = numbers.map { $0 * 2 }
    print(doubledNumbers) // [0, 2, 4, 6, 8]
  • filter
    • 내부의 값을 걸러서 새로운 컨테이너로 추출
    let numbers: [Int] = [0, 1, 2, 3, 4]
    var filtered: [Int] = [Int]()
    
    for number in numbers {
        if number % 2 == 0 {
            filtered.append(number)
        }
    }
    print(filtered) // [0, 2, 4]
    
    let evenNumbers: [Int] = numbers.filter { (number: Int) -> Bool in
        return number % 2 == 0
    }
    // 매개변수, 반환 타입, 반환 키워드(return) 생략, 후행 클로저
    let oddNumbers: [Int] = numbers.filter {
        $0 % 2 != 0
    }
    print(evenNumbers) // [0, 2, 4]
  • reduce
    • 하나로 통합
    let someNumbers: [Int] = [2, 8, 15]
    var result: Int = 0
    
    // someNumbers의 모든 요소를 더합니다
    for number in someNumbers {
        result += number
    }
    print(result) // 25
    
    // 초깃값이 0이고 someNumbers 내부의 모든 값을 더합니다.
    let sum: Int = someNumbers.reduce(0, { (first: Int, second: Int) -> Int in
        //print("\(first) + \(second)") 
    		//0 + 2
    		//2 + 8
    		//10 + 15
        return first + second // 25
    })
    let sumFromThree = someNumbers.reduce(0) { $0 + $1 }