Published 2020. 12. 4. 17:27
728x90
반응형

스위프트에서 오류는 Error 라는 프로토콜을 준수하는 타입의 값을 통해 표현된다.
Error 프로토콜은 사실상 요구사항이 없는 빈 프로토콜일 뿐이지만, 오류를 포현하기 위한 타입(주로 열거형)은
이 프로토콜을 채택한다.
스위프트의 열거형은 오류의 종류를 나타내기에 아주 적합한 기능이다.
연관 값을 통해 오류에 관한 부가 정보를 제공할 수도 있다.

프로그램 내에서 자판기를 작동시키려고 할 때 발생하는 오류상황을 구현해보았다.

오류 표현하기

Error 프로토콜과 주로 열거형을 통해서 오류를 표현한다.

// 자판이 동작 오류의 종류를 표현한 열거형
enum VendingMachineError: Error  {
    case invalidInput
    case insufficientFunds(moneyNeeded: Int)
    case outOfStock
}

이제 자판기 동작 도중 발생한 오류를 던지는 메서드를 구현해본다.
오류 발생의 여지가 있는 메서드는 throws 를 사용하여 오류를 내포하는 함수임을 표시한다.

자판기 동작 관련 클래스는 다음과 같이 구현했다.

class VendingMachine {
    // 저장 프로퍼티 설정
    let itemPrice: Int = 100
    var itemCount: Int = 5
    var deposited: Int = 0
    
    //
    func receiveMoney(_ money: Int) throws {
        // 돈이 없을 때 오류 발생
        guard money > 0 else {
            throw VendingMachineError.invalidInput
        }
        
        // 자판기에 돈을 넣는다.
        self.deposited += money
        print("\(money) 원 받음")
    }
    
    // 자판 관련 메서드
    func vend(numberOfItems numberOfItemsToVend: Int) throws -> String {
        // 종류가 0개보다 많은지 확인
        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) 개 제공"
    }
}

주석으로 해당 동작에 대한 설명을 적어놓았다.

이제 오류를 발생시켜 확인해야하는데 던져진 오류를 처리하는 코드도 작성해야한다.
던져진 오류가 무엇이고, 오류가 무엇인지 판단해서 문제를 해결하거나 다른 방법을 시도한다든지
사용자에게 알리고 선택 권한을 넘기는 등 다음 동작을 어떻게 처리해야할지 등에 대해서 작성해야 한다.

오류 처리하기

오류 발생 여지가 있는 throws 함수는 try를 사용해서 호출해야 한다.
throw 키워드를 사용하면 함수, 메서드, 이니셜라이저는 오류를 던질 수 있다. 그리고 재정의 될 수 없다.
따라서 try 와 do-catch, try?, try! 등에 대해서 알아보자.

do-catch

가장 정석적인 방법으로 모든 오류 케이스에 대응한다.
do-catch 구문은 다음과 같이 사용할 수 있었다.

// do-catch
// 돈을 0원 넣었을 경우
do {
    try machine.receiveMoney(0)
} catch VendingMachineError.invalidInput {
    print("입력이 잘못되었습니다.")
} catch VendingMachineError.insufficientFunds(let moneyNeeded) {
    print("\(moneyNeeded) 원이 부족합니다.")
} catch VendingMachineError.outOfStock {
    print("수량이 부족합니다.")
}

하나의 catch 블럭에서 switch 구문을 사용해서 분류할 수도 있다. 위에서 사용한 것과 다를 것 없다는 점.

// swich 구문을 통해서 구현할 수 있다.
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)")
    }
}

만약 각 케이스별로 오류를 처리하지 않아도 된다면 catch 구문을 간략하게 해도 된다.
또는 사용할 일이 없으면 사용하지 않아도 된다.

// catch 구문을 간략하게 해준다.
do {
    result = try machine.vend(numberOfItems: 4)
} catch {
    print(error)
}


// 케이스별로 오류 처리를 하지 않아도 된다면 catch 구문을 사용하지 않아도 된다.
do {
    result = try machine.vend(numberOfItems: 4)
}

try?

별도로 오류처리 결과를 통보받지 않고 오류가 발생했으면 결과값을 nil 로 돌려받을 수 있다.
정상동작 후에는 옵셔널 타입으로 정상 반환 값을 돌려 받는다.

result = try? machine.vend(numberOfItems: 2)
result // 2개 제공

result = try? machine.vend(numberOfItems: 2)
result // nil

try!

오류가 발생하지 않을 것이라는 강력한 확신을 가질 때 try! 를 사용하면 정상동작 후에 바로 결과값을 돌려받는다.
오류가 발생하면 런타임 오류가 발생하여 애플리케이션 동작이 중지된다.

result = try! machine.vend(numberOfItems: 1)
result // 1개 제공

// 런타임 오류 발생
result = try! machine.vend(numberOfItems: 1)
result

 

추가적으로 rethrows, defer 에 대해서 알아보면 좋다고 한다.
그래서 찾아보았다.

rethrows

throw와 비슷한 기능을 하지만 rethrows는 매개변수로 전달받은 "함수"가 오류를 발생시킬때 사용한다.
최소 하나 이상의 오류 발생 가능한 함수를 매개변수로 전달받아야 사용 가능하다.

부모 클래스의 rethrows 메서드를 자식 클래스의 throws 메서드로 오버라이드 불가능
부모 클래스의 throws 메서드를 자식 클래스의 rethrows 메서드로 오버라이드 가능
프로토콜의 throw 요구 함수를 rethrows 를 사용하여 부합시키지 못한다.
프로토콜의 rethrow 요구 함수를 throws 를 사용하여 부합시킬 수 있다.

라고 설명이 되어져 있는 여러 글들을 보았지만 어떻게 사용하는 것인지 감은 오지만
사실 rethrows 에 대해서는 잘 이해가 되지 않아서 다시 한 번 찾아봐야겠다..ㅎㅎ

defer

현재 코드 블럭을 나가기 전에 꼭 실행되어야 하는 코드가 실행되도록 보장해주는 것
함수, 메서드, 반복문, 조건문 등 코드 블럭 어디에서든 사용할 수 있다.
defer 구문 내부에서는 break, return 등과 같이 블럭을 빠져나가는 코드를 작성하면 안된다 !
여러 개의 defer 구문이 하나의 블럭 내부에 속해 있다면 맨 마지막에 작성된 구문부터 역순으로 수행된다.
따라서 defer 구문은 현재 블럭이 끝날때까지 기다렸다가 끝나기 전에 실행된다.

func printStringNumbers() {
    defer { print("1") }
    defer { print("2") }
    defer { print("3") }
    print("4")
}

printStringNumbers()

========== 출력 결과 ==========
4
3
2
1

예제를 확인해보면 4 3 2 1 순서대로 출력되는 것을 통해서 맨 마지막에 작성된 구문부터 실행되는 것을 확인할 수 있었다.

 

 

- 참고 사이트 - 

www.edwith.org/boostcamp_ios/lecture/11321

 

[LECTURE] 28. 오류 처리 : edwith

:: 오류 처리 :: 1. 오류 처리 스위프트에서 오류(Error)는 Error라는 프로토콜을 준수하는 타입의 값을 통해 표현됩니다. Error 프로토콜은 사실상 요구사항이 없는 ... - 부스트코스

www.edwith.org

zetal.tistory.com/entry/swift-기초문법-31-throw-do-catch?category=787504

 

[swift] throw, do-catch, rethrows, defer

[swift 기초문법] - throw, do-catch, rethrows, defer throw throw 키워드를 쓰면 함수, 메서드, 이니셜라이저는 오류를 던질 수 있습니다. throws 키워드를 사용한 함수는 동작 도중 오류가 발생하면 호출한 코..

zetal.tistory.com

redsubmarine.github.io/2016/10/21/Swift-3.0-의-throws,-rethrows-에-대하여....html

 

Swift 3.0 의 throws, rethrows 에 대하여...

반년전즈음 스터디모임에서 Swift 3.0의 Array 에 대해 다루다가 map 이라는 함수를 보고 의문점이 생겼다. 분명히 내가 기억하기로는 Swift 2.2에서 Array의 map 함수는 Array.map(transform: T -> U) 이와 같이

redsubmarine.github.io

blog.naver.com/PostView.nhn?blogId=taerg89&logNo=221731014261

 

728x90
반응형
복사했습니다!