Swift - 17. 에러 처리 Error Handling

지우개·2022년 4월 5일
0

Swift study

목록 보기
6/15
post-thumbnail

에러 처리란? 프로그램 실행 시 에러가 발생하면 상황에 대해 적절한 처리를 하는 것
Swift에서는 런타임에 에러가 발생한 경우 처리를 위해 에러의 발생, 감지, 증식, 조작을 지원하는 일급 클래스를 제공한다.

에러의 표시와 발생 Representing and Throwing Errors

에러는 Error 프로토콜을 따르는 타입의 값으로 표현됨
비어있는 이 프로토콜은 프로토콜을 따르는 타입이 에러 처리를 위해 사용될 수 있다는 것을 나타냄

Swift의 열거형은 특히 이런 에러를 그룹화하고 추가적인 정보를 제공하기 적합하다.

enum VendingMachineError: Error {
     case invalidSelection
     case insufficientFunds(coinsNeeded: Int)
     case outOfStock
}

// 에러를 발생시키기 위해 throw 구문을 사용함 - 5코인이 더 필요하다
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

에러 처리 Handling Errors

에러가 발생하면 특정 코드 영역이 해당 에러를 처리하도록 해야 함.
문제를 해결하거나, 우회하는 방법을 시도하거나, 사용자에게 실패 상황을 알리는 등의 방법

Swift에서 에러를 처리하는 방법
1. 에러가 발생한 함수에서 리턴 값으로 에러를 반환해 해당 함수를 호출한 코드에서 에러를 처리하도록 하는 방법
2. do-catch 구문을 사용하는 방법
3. 옵셔널 값을 반환하는 방법
4. assert를 사용해 강제로 크래쉬를 발생시키는 방법

1. 에러를 발생시키는 함수 사용하기

throwing function :

  • throw 키워드를 파라미터 뒤에 붙여 표현
  • 함수 내부에서 에러를 만들어 함수가 호출된 곳에 전달한다.
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String

오직 throwing function만이 에러를 발생시킬 수 있다.
만약 throwing function이 아닌 함수에서 throw가 발생한다면 반드시 그 함수 내에서 throw에 대해 처리되어야 한다.

다음 예제는 VendingMachineError를 발생하는 vend(itemNamed:) 메소드를 가진 예제

struct Item {
    var price: Int
    var count: Int
}

class VendingMachine {
    var inventory = [
        "Candy Bar": Item(price: 12, count: 7),
        "Chips": Item(price: 10, count: 4),
        "Pretzels": Item(price: 7, count: 11)
    ]
    var coinsDeposited = 0

    func vend(itemNamed name: String) throws {
        guard let item = inventory[name] else {
            throw VendingMachineError.invalidSelection
        }

        guard item.count > 0 else {
            throw VendingMachineError.outOfStock
        }

        guard item.price <= coinsDeposited else {
            throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
        }

        coinsDeposited -= item.price

        var newItem = item
        newItem.count -= 1
        inventory[name] = newItem

        print("Dispensing \(name)")
    }
}

vend(itemNamed:) 메소드에서 guard 구문을 사용해 함수에서 에러를 발생시키고 빠르게 함수를 탈출할 수 있도록 함
vend(itemNamed:) 메소드는 에러를 발생시키기 때문에 이 메소드를 호출하는 메소드는 반드시
do-catch, try?, try! 등의 구문을 사용해 에러를 처리해야 함

아래 예제의 buyFavoriteSnack(person:vendingMachine:) 또한 에러를 발생시키는 함수인데 vend(itemNamed:) 메소드에서 발생한 에러는 buyFavoriteSnack(person:vendingMachine:) 함수가 호출된 곳에까지 전해진다.

let favoriteSnacks = [
    "Alice": "Chips",
    "Bob": "Licorice",
    "Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try vendingMachine.vend(itemNamed: snackName)
}

주어진 사람이 가장 좋아하는 스낵을 확인하고 vend(itemNamed:) 메소드를 호출해 구매를 시도한다. vend(itemNamed:) 메소드는 에러를 발생시킬 수 있으므로 try 키워드를 붙여 호출한다.

에러 발생 초기자는 throwing function과 같은 방법으로 에러를 발생시킬 수 있다.
아래 예제의 PurchasedSnack 구조체의 초기자는 초기화 단계의 일부분으로써 에러를 발생시킬 수 있는 함수이다. 그리고 초기자가 실행될 때 발생한 에러는 이 초기자를 호출한 곳에 전달된다.

struct PurchasedSnack {
    let name: String
    init(name: String, vendingMachine: VendingMachine) throws {
        try vendingMachine.vend(itemNamed: name)
        self.name = name
    }
}

2. Do-Catch를 이용해 에러를 처리하기 Handling Error Using Do-Catch

do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
} catch {
    statements
}
`
``
- `catch` 구문 뒤에 어떤 에러인지 적고 어떻게 처리할지 명시할 수 있다.
- 먄약 `catch` 구문 뒤에 에러 종류를 명시하지 않으면 발생하는 모든 에러를 지역 상수인 error로 바인딩한다.

다음 예제는 `VendingMachineError` 열거형의 모든 세 가지 에러 종류에 대해 처리하는 코드이다.
```swift
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
    try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
    print("Success! Yum.")
} catch VendingMachineError.invalidSelection {
    print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
    print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
    print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
} catch {
    print("Unexpected error: \(error).")
}
// Prints "Insufficient funds. Please insert an additional 2 coins."

위 예제에서 buyFavoriteSnack(person:vendingMachine:) 함수는 에러를 발생할 수 있으므로 try 표현식으로 호출된다. 에러가 발생하면 즉시 catch 절로 전달되어 적절한 처리를 할 수 있게 한다.
만약 발생한 에러 종류를 처리하는 catch 구문이 없다면 에러는 마지막 catch 구문에 걸리게 되어 지역 에러 상수인 error로 처리할 수 있다.
아무 에러도 발생하지 않는다면 do 구문이 실행된다.

catch 구문에서 발생 가능한 모든 에러에 대해 반드시 종류별로 처리할 필요는 없다.
에러를 처리하는 적절한 catch 구문이 없다면 그 코드를 둘러싼 곳에 에러가 발생한다.
발생된 에러는 반드시 관련 특정 코드 영역 안에서 처리되어야 한다.
에러를 발생시키지 않는 함수에서는 관련 do-catch 구문에서 에러를 반드시 처리해야 하고,
에러를 발생시키는 함수에서는 에러를 do-catch 구문에서 처리하거나 함수를 호출한 곳에서 반드시 에러를 처리해야 한다.

예를 들어, 모든 에러에 대해 기술하는 대신 다음 예제와 같이 처리할 수 있다.

func nourish(with item: String) throws {
    do {
        try vendingMachine.vend(itemNamed: item)
    } catch is VendingMachineError {    // 모든 VendingMachineError 구분을 위해 is를 사용
        print("Invalid selection, out of stock, or not enough money.")
    }
}

do {
    try nourish(with: "Beet-Flavored Chips")
} catch {
    print("Unexpected non-vending-machine-related error: \(error)")
      // 여기에서 처럼 catch를 그냥 if-else에서 else 같이 사용 가능
}
// Prints "Invalid selection, out of stock, or not enough money."

3. 에러를 옵셔널 값으로 변환하기 Converting Errors to Optional Values

try? 구문을 사용해 에러를 옵셔널 값으로 변환할 수 있음
에러가 try? 표현 내에서 발생하면 그 표현의 값은 nil이 됨

에러 발생을 중지하기 Disabling Error Propagation

함수나 메소드에서 에러가 발생하지 않을 것이라고 확신하는 경우 try!를 사용
혹은 runtime assertion을 사용해 에러가 발생하지 않도록 할 수 있음


정리 액션 기술 Specifying Cleanup Actions

defer 구문

  • defer 키워드와 나중에 실행될 구문으로 구성
  • 함수가 종료된 후 파일 스트림을 닫거나 사용했던 자원을 해지하는 등의 일을 할 수 있음
  • 여러 개 있는 경우 가장 마지막 줄부터 실행됨, 즉 bottom-up 순
  • 현재 범위가 종료될 때까지 실행을 연기함
func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file) // block이 끝나기 직전에 실행, 주로 자원 해제나 정지에 사용
        }
        while let line = try file.readline() {
            // Work with the file.
        }
        // close(file) is called here, at the end of the scope.
    }
} 

위 예제는 open(_:) 함수에 close(_:)에 대한 호출이 있는지 확인하기 위해 defer 구문을 사용한다.

0개의 댓글