티스토리 뷰

Swift 또한 런타임 에러를 처리하기 위한 에러의 throwing, chaching, propagating, manipulating 을 지원하는 일급 클래스를 제공한다.

 

바로 예제와 함께 알아보자.


1. 에러의 표시와 발생

Swift의 Enumdms 값으로 표현되는 에러를 그룹화하고 추가적인 정보를 제공하기에 적합하다. 

예를 들어, 게임 안에서 판매기기 동작의 에러 상황을 다음과 같이 표현할 수 있다.

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

또한 위 에러를 발생시키려면 throw 구문을 사용하여 아래와 같이 insufficientFunds 에러를 발생시킬 수 있다.

throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

 


2. 에러 처리

에러가 발생하면 특정 코드 영역이 해당 에러를 처리하도록 해야한다. 

 

Swift에서 4가지 에러 처리 방법이 있다.

 1) 리턴 값으로 에러를 반환하여 해당 함수를 호출한 코드에서 에러를 처리하도록 하는 방법

 2) do-catch 구문을 사용하는 방법

 3) 옵셔널 값을 반환하는 방법

 4) assert를 사용하여 강제로 크래쉬를 발생시키는 방법

 

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

어떤 함수, 메서드 혹은 생성자가 에러를 발생 시킬 수 있다는 것을 알리기 위하여 throw 키워드를 함수 선언부의 파라미터 뒤에 붙일 수 있다. throw가 붙은 함수를  throwing function이라고 부른다.

func canThrowErrors() throws -> String

func cannotThrowErrors() -> String

throwing function은 함수 내부에서 에러를 만들어 함수가 호출된 곳에 전달한다.

 

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: 8, 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
		}
		
		coinsDeposited -= item.price
		
		var newItem = item
		newItem.count -= 1
		inventory[name] = newItem
		
		print("Dispensing \(name)")
	}
}

 Vend(itemNamed:) 메서드의 구현에서 guard 구문을 사용해 snack을 구매하는 과정에서 에러가 발생하면 함수에서 에러를 발생시키고 빠르게 함수를 탈출할 수 있도록 한다. 

Vend(itemNamed:) 메서드는 에러를 발생시키기 때문에 이 메서드를 호출하는 메서드는 반드시 do-catch, try?, tyr! 등의 구문을 사용하여 에러를 처리하여야 한다.

 

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)
}

위 예제에서는 buyFavoriteSnack(person:, vendingMachine:) 함수가 주어진 사람의 가장 좋아하는 스낵이 뭔지 확인하고 그것을 vend 메서드를 호출하여 구매를 시도한다. vend 메서드는 에러를 발생 시킬 수 있기 때문에 메서드 호출 앞에 try 키워드를 사용한다.

 

마찬가지로 이니셜라이저에서도 try로 에러 발생 여지를 남길 수 있다.

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

 

- Do-Catch를 이용하여 에러를 처리하기

do-catch를 이용하여 에러를 처리하는 코드 블럭을 작성할 수 있다. 만약 에러가 do 구문 안에서 발생한다면, 발생하는 에러의 종류를 catch 구문으로 구분하여 처리할 수 있다.

 

아래는 do-catch 구문의 일반적인 형태이다.

do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
} catch {
    statements
}
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 구문에 전달하여 각 에러 Enum에 맞는 패턴을 매칭하여 각 코드 블럭을 실행하며, 패턴에 맞는 catch문이 없다면 맨 아래 default 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."

 위와 같이 is 키워드를 사용하여 모든 VendingMachineError를 캐치할 수 있다.

 

- 에러를 옵셔널 값으로 변환하기

try? 구문을 사용하여 에러를 옵셔널 값으로 변환할 수 있다. 만약 에러가 try? 표현 내에서 발생한다면, 그 표현의 값은 nil 이 된다.

func someThrowingFunction() throws -> Int {
    // ...
}

let x = try? someThrowingFunction()

let y: Int?

do{
    y = try someThrowingFunction()
} catch {
    y = nil
}

위 x와 y는 동일하게 someThrowingFunction이 에러를 발생시키면 nil로 할당이 되고, 에러가 발생하지 않는다면 함수의 반환 값으로 할당을 받는다.

 

함수나 메서드에서 아무런 에러가 발생하지 않을 것이라고 확신하는 경우 try!를 사용할 수 있다. 

 

- 정리 액션 기술

defer 구문을 이용해 함수가 종료된 후 파일 스트림을 닫거나, 사용했던 자원을 해지 하는 등의 일을 할 수 있다. defer가 여러 개가 있는 경우 가장 마지막 줄부터 실행된다. 즉, defer는 bottom up 순으로 실행된다.

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
        }
    }
}

 

Comments