Swift의 오류 처리는 Cocoa 및 Objective-C의 NSError 클래스를 사용하는 오류 처리 패턴과 상호 운용됩니다.
이 클래스에 대한 자세한 내용은 Swift에서 코코아 오류 처리를 참조하세요.
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
Swift의 오류 처리는 try, catch 및 throw 키워드를 사용하는 다른 언어의 예외 처리와 유사합니다.
Objective-C를 포함한 많은 언어의 예외 처리와 달리 Swift의 오류 처리에는 계산 비용이 많이 들 수 있는 프로세스인 호출 스택 해제가 포함되지 않습니다.
따라서 throw 문의 성능 특성은 return 문의 성능 특성과 비슷합니다.
함수, 메서드 또는 초기화 프로그램에서 오류가 발생할 수 있음을 나타내려면 함수 선언에서 매개변수 뒤에 throw 키워드를 작성합니다.
throws로 표시된 함수를 throwing 함수라고 합니다.
함수가 반환 유형을 지정하는 경우 반환 화살표(->) 앞에 throw 키워드를 작성합니다.
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
throwing 함수는 내부에서 throw된 오류를 호출된 범위로 전파합니다.
throw하는 함수만 오류를 전파할 수 있습니다. 던지지 않는 함수 내에서 발생한 모든 오류는 함수 내에서 처리되어야 합니다.
아래 예제에서 VendingMachine 클래스에는 요청된 항목을 사용할 수 없거나, 재고가 없거나, 현재 예치된 금액을 초과하는 비용이 있는 경우 적절한 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 문을 사용하여 메서드를 일찍 종료하고 스낵 구매에 대한 요구 사항이 충족되지 않는 경우 적절한 오류를 발생시킵니다.
throw 문은 즉시 프로그램 제어를 전송하므로 이러한 모든 요구 사항이 충족되는 경우에만 항목이 판매됩니다.
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)
}
이 예제에서 buyFavoriteSnack(person: vendingMachine:) 함수는 주어진 사람이 가장 좋아하는 간식을 찾고 vend(itemNamed:) 메서드를 호출하여 구매를 시도합니다.
throwing initializer는 throwing 함수와 같은 방식으로 오류를 전파할 수 있습니다.
예를 들어, 아래 목록의 PurchasedSnack 구조에 대한 이니셜라이저는 초기화 프로세스의 일부로 throwing 함수를 호출하고 발생하는 오류를 호출자에게 전파하여 처리합니다.
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 {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} catch pattern 3, pattern 4 where condition {
statements
} catch {
statements
}
catch 다음에 패턴을 작성하여 해당 절이 처리할 수 있는 오류를 나타냅니다.
catch 절에 패턴이 없으면 이 절은 모든 오류와 일치하고 오류를 error라는 로컬 상수에 바인딩합니다.
예를 들어 다음 코드는 VendingMachineError 열거의 세 가지 경우 모두와 일치합니다.
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되고 로컬 오류 상수에 바인딩됩니다.
오류가 발생하지 않으면 do 문의 나머지 문이 실행됩니다.
catch 절은 do 절의 코드가 던질 수 있는 모든 가능한 오류를 처리할 필요가 없습니다.
catch 절이 오류를 처리하지 않으면 오류가 주변 범위로 전파됩니다.
그러나 전파된 오류는 일부 주변 범위에서 처리해야 합니다.
던지지 않는 함수에서 바깥쪽 do-catch 문은 오류를 처리해야 합니다.
던지는 함수에서 do-catch 문을 둘러싸거나 호출자가 오류를 처리해야 합니다.
오류가 처리되지 않고 최상위 범위로 전파되면 런타임 오류가 발생합니다.
예를 들어 위의 예제는 VendingMachineError가 아닌 모든 오류가 대신 호출 함수에 의해 catch되도록 작성할 수 있습니다.
func nourish(with item: String) throws {
do {
try vendingMachine.vend(itemNamed: item)
} catch is VendingMachineError {
print("Couldn't buy that from the vending machine.")
}
}
do {
try nourish(with: "Beet-Flavored Chips")
} catch {
print("Unexpected non-vending-machine-related error: \(error)")
}
// Prints "Couldn't buy that from the vending machine."
Nutrition(with:) 함수에서 vend(itemNamed:)가 VendingMachineError 열거의 경우 중 하나인 오류를 발생시키면 Nutrition(with:)은 메시지를 출력하여 오류를 처리합니다.
그렇지 않으면, Nutrition(with:)이 오류를 호출 사이트로 전파합니다. 그런 다음 일반 catch 절에 의해 오류가 포착됩니다.
여러 관련 오류를 포착하는 또 다른 방법은 catch 뒤에 쉼표로 구분하여 나열하는 것입니다.
func eat(item: String) throws {
do {
try vendingMachine.vend(itemNamed: item)
} catch VendingMachineError.invalidSelection, VendingMachineError.insufficientFunds, VendingMachineError.outOfStock {
print("Invalid selection, out of stock, or not enough money.")
}
}
당신은 try?를 사용해 선택적 값으로 변환하여 오류를 처리합니다.
try를 평가하는 동안 오류가 발생하면 ? 표현식에서 표현식의 값은 nil입니다.
예를 들어 다음 코드에서 x와 y는 동일한 값과 동작을 갖습니다.
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
someThrowingFunction()에서 오류가 발생하면 x와 y의 값은 nil입니다.
그렇지 않으면 x 및 y 값은 함수가 반환한 값입니다.
x와 y는 someThrowingFunction()이 반환하는 모든 유형의 선택 사항입니다.
여기서 함수는 정수를 반환하므로 x와 y는 선택적 정수입니다.
try?를 사용해 모든 오류를 동일한 방식으로 처리하려는 경우 간결한 오류 처리 코드를 작성할 수 있습니다.
예를 들어 다음 코드는 데이터를 가져오기 위해 여러 접근 방식을 사용하거나 모든 접근 방식이 실패하면 nil을 반환합니다.
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
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 문을 사용할 수 있습니다.