프로그램이 오류를 일으켰을 때 이것을 감지하고 회복시키는 일련의 과정입니다. 주의할 점은 스위프트의 오류처리 기능을 통해 시스템에서 발생한 오류를 처리할 수 있다는 것이 아님을 명심해야 합니다.
스위프트에는 오류는 Error라는 프로토콜을 준수하는 타입의 값을 통해 표현됩니다. Error 프로토콜은 사실상 요구사항이 없는 빈 프로토콜일 뿐이지만, 오류를 표현하기 위한 타입(주로 열거형)은 이 프로토콜을 채택합니다.
스위프트의 열거형은 오류의 종류를 나타내기에 아주 적합한 기능입니다. 연관 값을 통해 오류에 관한 부가 정보를 제공할 수도 있습니다.
자판기 동작에 대한 오류의 종류를 열거형으로 표현해보겠습니다.
enum vendingMachineError: Error {
case invalidSelection // 유효하지 않은 선택
case insufficientFunds(coinsNeeded: Int) // 금액 부족, coinsNeeded 필요한 코인 수
case outOfStock // 품절
}
그렇다면 이 오류로 인해 다음에 행할 동작이 정상적으로 진행되지 않을 때라면 오류를 던져(Throw Error)주면 됩니다. 오류를 던져줄 때는 throw 구문을 사용합니다.
오류를 던질 수도 있지만 오류가 던져지는 것에 대비하여 던져진 오류를 처리하기 위한 코드도 작성해야 합니다.
스위프트에는 오류를 처리하기 위한 네 가지 방법이 있습니다.
try 키워드로 던져진 오류를 받을 수 있습니다. try 키워드는 try 또는 try?, try! 등으로 표현할 수 있습니다.
함수, 메서드, 이니셜라이저의 매개변수 뒤에 throws 키워드를 사용하면 해당 함수, 메서드, 이니셜라이저는 오류를 던질 수 있습니다.
💡 throws는 함수나 메서드의 자체 타입에도 영향을 미칩니다. 즉, throws 함수나 메서드는 같은 이름의 throws가 명시되지 않는 함수나 메서드와 구분됩니다. 또, throws를 포함한 함수, 메서드, 이니셜라이저는 일반 함수, 메서드, 이니셜라이저로 재정의할 수 있습니다. 반대로 일반 함수, 메서드, 이니셜라이저는 throws 함수, 메서드, 이니셜라이저로 재정의할 수 있습니다.enum VendingMachineError: Error {
case invalidSelection // 유효하지 않은 선택
case insufficientFunds(coinsNeeded: Int) // 금액 부족, coinsNeeded 필요한 코인 수
case outOfStock // 품절
}
struct Item {
var price: Int
var count: Int
}
class VendingMachine {
var inventory: [String : Item] = [
"막대사탕" : Item(price: 12, count: 7),
"감자칩" : Item(price: 10, count: 4),
"비스켓" : Item(price: 7, count: 11)
]
var coinsDeposited: Int = 0
private func 제공(snack: String) {
print(snack + " 제공")
}
func vend(itemName: String) throws {
guard let item = inventory[itemName] else {
throw VendingMachineError.invalidSelection
}
guard item.count > 0 else {
throw VendingMachineError.outOfStock
}
guard item.price < self.coinsDeposited else {
throw VendingMachineError.insufficientFunds(
coinsNeeded: (item.price - self.coinsDeposited)
)
}
self.coinsDeposited -= item.price
var newItem = item
newItem.count -= 1
self.inventory[itemName] = newItem
self.제공(snack: itemName)
}
}
let 먹고싶은목록 = [
"피터": "감자칩",
"채드": "막대사탕",
"히로": "초콜릿"
]
func 자판기사용해보기(person name: String, vendingMachine: VendingMachine) throws {
let snackName = 먹고싶은목록[name] ?? "비스켓"
try vendingMachine.vend(itemName: snackName)
}
let machine: VendingMachine = VendingMachine()
machine.coinsDeposited = 30
for (person, snack) in 먹고싶은목록 {
print(person, snack)
try 자판기사용해보기(person: person, vendingMachine: machine)
}
// 피터 감자칩
// 감자칩 제공
// 채드 막대사탕
// 막대사탕 제공
// 히로 초콜릿
// 오류 발생!
// Playground execution terminated: An error was thrown and was not caught:
// __lldb_expr_59.VendingMachineError.invalidSelection
오류를 던질 수 있는 함수, 메서드, 이니셜라이저를 호출하는 코드는 반드시 오류를 처리할 수 있는 구문을 작성해주어야 합니다.
오류를 받은 코드가 적절히 오류를 처리해주지 않는다면 이후의 코드는 작동하지 않습니다. 던져진 오류를 처리하는 방법에 대해 알아보겠습니다.
do {
try 오류 발생 가능코드
오류가 발생하지 않으면 실행할 코드
} catch 오류 패턴1 {
처리코드
} catch 오류 패턴2 {
처리코드
}
func 자판기를사용(itemName: String, 자판기: VendingMachine) {
do {
try 자판기.vend(itemName: itemName)
} catch VendingMachineError.invalidSelection {
print("유효하지 않은 선택입니다.")
} catch VendingMachineError.insufficientFunds(let 부족한_동전) {
print("자금 부족 - 동전 \(부족한_동전)개를 추가로 지급해주세요.")
} catch {
print("그 외 오류 처리: ", error)
}
}
let machine: VendingMachine = VendingMachine()
machine.coinsDeposited = 20
for (person, snack) in 먹고싶은목록 {
print(person, snack)
try 자판기를사용(itemName: snack, 자판기: machine)
}
// 피터 감자칩
// 감자칩 제공
// 채드 막대사탕
// 자금 부족 - 동전 2개를 추가로 지급해주세요.
// 히로 초콜릿
// 유효하지 않은 선택입니다.
try?를 사용하여 옵셔널 값으로 변환하여 오류를 처리할 수도 있습니다. try? 표현을 통해 동작하던 코드가 오류를 던지면 그 코드의 반환 값은 nil이 됩니다.
let value = try? 오류를던지는함수(shouldThrowError: true)
print(value)
let newValue = try? 오류를던지는함수(shouldThrowError: false)
print(newValue)
// nil
// Optional(100)
오류가 발생하지 않을 것이라는 전제하에 try! 표현을 사용할 수 있습니다. 다만 실제 오류가 발생하면 런타임 오류가 발생하여 프로그램이 강제로 종료됩니다.
함수나 메서드는 rethrows 키워드를 사용하여 자신의 매개변수로 전달받은 함수가 오류를 던진다는 것을 나타낼 수 있습니다. rethrows 키워드를 사용하여 다시 던지기(Rethrowing)가 가능하게 하려면 최소 하나 이상의 오류 발생 가능한 함수를 매개변수로 전달받아야 합니다.
func 오류를던지는함수() throws {
enum SomeError: Error {
case justError
}
throw SomeError.justError
}
func 어떤함수(callback: () throws -> Void) rethrows {
enum AnotherError: Error {
case justAnotherError
}
do {
// 매개변수로 전달한 오류 던지기 함수가 아니므로
// catch 절에서 제어할 수 있습니다.
try callback()
} catch {
throw AnotherError.justAnotherError
}
do {
// 매개변수로 전달한 오류 던지기 함수가 아니므로
// catch 절에서 제어할 수 없습니다.
try 오류를던지는함수()
} catch {
// 오류 발생!
throw AnotherError.justAnotherError
}
// rethrows시 catch 절 외부에서 단독으로 오류를 던질 수는 없습니다.
// 오류 발생!
throw AnotherError.justAnotherError
}
부모클래스의 rethrows 메서드는 자식클래스에서 throws 메서드로 재정의할 수 없습니다. 그러나 부모클래스의 throws 메서드는 자식클래스에서 rethrows 메서드로 재정의할 수 있습니다.
defer 구문을 사용하여 현재 코드 블록을 나가기 전에 꼭 실행해야하는 코드를 작성해줄 수 있습니다. 즉, defer 구문은 코드가 블록을 어떤 식으로 빠져나가든 간에 꼭 실행해야 하는 마무리 작업을 할 수 있도록 도와줍니다.
예를 들어 코드가 닫히기 전에 파일을 꼭 닫는 코드를 추가해야하는 경우에 유용합니다.
func 파일을씁니다() {
defer {
일단파일을닫으세요()
}
if ... {
return
}
if ... throws {
throw
}
// 처리 끋
}
defer 구문 내부에는 break, return 등과 같이 구문을 빠져나갈 수 있는 코드 또는 오류를 던지는 코드는 작성하면 안 됩니다.
여러 개의 defer 구문이 하나의 범위 내부에 속해 있다면 맨 마지막에 작성된 구문부터 역순으로 실행됩니다.
func someFunc() {
print(1)
defer {
print(2)
}
do {
defer {
print(3)
}
print(4)
}
defer {
print(5)
}
print(6)
}
someFunc()
// 1
// 4
// 3
// 6
// 5
// 2
자료 출처: 야곰 스위프트 프로그래밍 3판
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."